WebDriver協議的純Clojure實現。使用該庫自動化瀏覽器,測試您的前端行為,模擬人類的動作或您想要的任何內容。
它以Etaoin Shrdlu的名字命名,這是一台在其上產生了一個神秘音符後活著的打字機。
由於0.4.0
,驅動程序實例是地圖,但不像以前那樣的原子。這是一個很難決定的解決方案,但是我們已經擺脫了原子來遵循我們的代碼中的Clojure方式。一般而言,您永遠不會刪除駕駛員或在其中存儲東西。現在,用於修改實例的所有內部功能現在只需返回新版本的地圖即可。如果您有swap!
或驅動程序代碼中類似的內容,請在更新之前對代碼進行重構。
由於0.4.0
,庫支持WebDriver操作。操作是批處理髮送給驅動程序的命令。請參閱TOC中的詳細相關部分。
由於0.4.0
,Etaoin可以播放在Interactive Selenium IDE中創建的腳本文件。請參閱下面的相關部分。
Ct t
進行單元測試。歡迎您將您的公司提交該列表。
安裝有兩個步驟:
etaoin
安裝到您的Clojure代碼中將以下內容添加到:dependencies
project.clj
文件中的依賴項向量:
[etaoin "0.4.6"]
與Clojure 1.9及以上合作。
此頁面提供了有關如何安裝驅動程序的說明,您需要自動化瀏覽器。
安裝Chrome和Firefox瀏覽器,從官方網站下載它們。在所有平台上都不會有問題。
安裝您需要的特定驅動程序:
Google Chrome驅動程序:
brew cask install chromedriver
2.28
版。 2.27
及以下具有與最大化窗口有關的錯誤(請參閱[[[故障排除]])。Geckodriver,Firefox的驅動程序:
brew install geckodriver
phantom.js瀏覽器:
brew install phantomjs
Safari驅動程序(僅適用於Mac):
現在,檢查您的安裝啟動這些命令中的任何一個。對於每個命令,應啟動使用本地HTTP服務器的無盡進程。
chromedriver
geckodriver
phantomjs --wd
safaridriver -p 0
您可以為此庫啟動進行測試:
lein test
您會看到瀏覽器窗口串聯打開和關閉。測試使用具有特殊佈局的本地HTML文件來驗證大多數情況。
如果您有問題,請參見下面的故障排除部分
您可能會直接從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)
您會看到,任何功能都需要驅動程序實例作為第一個參數。因此,您可以使用doto
宏來簡化它:
( 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 ))
在這種情況下,您的代碼看起來像是為此目的而設計的DSL。
您可以使用fill-multi
縮短代碼:
( fill driver :login " login " )
( fill driver :password " pass " )
( fill driver :textarea " some text " )
進入
( fill-multi driver { :login " login "
:password " pass "
:textarea " some text " })
如果在瀏覽器會話期間發生任何例外,則外部過程可能會永遠懸掛,直到您手動殺死它為止。為了防止它,請with-<browser>
使用如下:
( with-firefox {} ff ; ; additional options first, then bind name
( doto ff
( go " https://google.com " )
...))
無論在會議期間發生什麼,無論如何,該過程都將停止。
諸如click
, fill
等之類的大多數功能都需要查詢術語以發現頁面上的元素。例如:
( click driver { :tag :button })
( fill driver { :id " searchInput " } " Clojure " )
該庫支持以下查詢類型和值。
:active
代表當前的活動元件。例如,在打開Google頁面時,它將光標聚焦在主要搜索輸入上。因此,無需手動點擊。例子:
( fill driver :active " Let's search for something " keys/enter)
任何指示元素ID的其他關鍵字。對於Google頁面,它是:lst-ib
或"lst-ib"
(也支持字符串)。註冊表很重要。例子:
( fill driver :lst-ib " What is the Matrix? " keys/enter)
帶有XPath表達式的字符串。手動編寫它們時要小心。檢查下面的Troubleshooting
部分。例子:
( fill driver " .//input[@id='lst-ib'][@name='q'] " " XPath in action! " keys/enter)
具有:xpath
或:css
鍵的映射,帶有相應語法的字符串表達式。例子:
( fill driver { :xpath " .//input[@id='lst-ib'] " } " XPath selector " keys/enter)
( fill driver { :css " input#lst-ib[name='q'] " } " CSS selector " keys/enter)
有關更多信息,請參見CSS選擇器手冊。
查詢可能是代表XPath表達式作為數據的任何其他地圖。規則是:
:tag
鍵代表標籤的名稱。當不通過時,它變成*
。:index
鍵擴展到尾隨[x]
子句中。例如,當您需要從表中選擇第三行時有用。:fn/
名稱空間,並擴展為特定的東西。示例:
找到第一個div
標籤
( query driver { :tag :div })
; ; expands into .//div
找到n-th div
標籤
( query driver { :tag :div :index 1 })
; ; expands into .//div[1]
找到具有類屬性等於active
標籤a
( query driver { :tag :a :class " active " })
; ; ".//a[@class="active"]"
通過其屬性找到形式:
( query driver { :tag :form :method :GET :class :message })
; ; expands into .//form[@method="GET"][@class="message"]
通過其文本找到一個按鈕(確切匹配):
( query driver { :tag :button :fn/text " Press Me " })
; ; .//button[text()="Press Me"]
使用“下載”文本查找第n個元素( p
, a
,whting):
( query driver { :fn/has-text " download " :index 3 })
; ; .//*[contains(text(), "download")][3]
找到一個具有以下類的元素:
( query driver { :tag :div :fn/has-class " overlay " })
; ; .//div[contains(@class, "overlay")]
找到一個在HREF中具有以下域的元素:
( query driver { :tag :a :fn/link " google.com " })
; ; .//a[contains(@href, "google.com")]
找到一個同時具有以下類的元素:
( query driver { :fn/has-classes [ :active :sticky :marked ]})
; ; .//*[contains(@class, "active")][contains(@class, "sticky")][contains(@class, "marked")]
找到啟用/禁用的輸入小部件:
; ; 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()]
查詢可能是由上述任何表達式組成的向量。在這樣的查詢中,每個下一個學期都會從上一個遞歸中搜索。
一個簡單的例子:
( click driver [{ :tag :html } { :tag :body } { :tag :a }])
您也可以將XPath和CSS表達式結合在一起(請注意XPath表達中的領先點:
( click driver [{ :tag :html } { :css " div.class " } " .//a[@class='download'] " ])
有時,您可能需要與查詢的第n個元素進行交互,例如,在要單擊此示例中的第二個鏈接時:
< 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 >
在這種情況下,您可以使用以下XPath表達式支持的:index
指令:
( click driver [{ :tag :li :class :search-result :index 2 } { :tag :a }])
或者,您可以將nth-Child Trick與CSS表達式使用:
( click driver { :css " li.search-result:nth-child(2) a " })
最後,也可以通過使用query-all
直接獲得nth元素:
( click-el driver ( nth ( query-all driver { :css " li.search-result a " }) 2 ))
請注意,在此處使用click-el
的使用,因為query-all
返回元素,而不是可以通過直接click
的選擇器。
query-tree
採用選擇器,就像樹一樣。每個下一個選擇器都從上一個選擇元素中查詢元素。拳頭選擇器依賴於發現元素,其餘的元素則使用find-elements-from
( query-tree driver { :tag :div } { :tag :a })
方法
{:tag :div} -> [div1 div2 div3]
div1 -> [a1 a2 a3]
div2 -> [a4 a5 a6]
div3 -> [a7 a8 a9]
因此結果將是[A1 ... A9]
要與通過查詢找到的元素進行交互,您必須將查詢結果傳遞給click-el
或fill-el
:
( click-el driver ( first ( query-all driver { :tag :a })))
因此,您可以將元素收集到矢量中,並隨時與它們互動:
( 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! " )
為了模擬人類輸入,您可以使用fill-human
函數。默認情況下啟用以下選項:
{ :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
您可以重新定義它們:
( 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)
對於與人類fill-human-multi
的多次輸入
( fill-human-multi driver { :login " login "
:pass " password "
:textarea " some text " }
{ :mistake-prob 0.5
:pause-max 1 })
click
函數觸發左鼠標單擊查詢術語找到的元素:
( click driver { :tag :button })
click
函數僅使用查詢發現的第一個元素,有時會導致單擊錯誤的項目。為了確保找到一個且僅找到一個元素,請使用click-single
函數。它的作用相同,但在查詢頁面返回多個元素時會引起例外:
( click-single driver { :tag :button :name " search " })
double-click
功能(Chrome,Phantom.js)很少在Web中使用雙擊,但可以使用雙擊。
( double-click driver { :tag :dbl-click-btn })
還有一堆“盲”點擊功能。它們觸發鼠標單擊當前鼠標位置:
( left-click driver)
( middle-click driver)
( right-click driver)
在單擊它們之前,另一串函數也可以執行相同的操作,但在單擊它們之前將鼠標指針移至指定元素:
( left-click-on driver { :tag :img })
( middle-click-on driver { :tag :img })
( right-click-on driver { :tag :img })
在新的背景選項卡中打開鏈接時,中間鼠標單擊很有用。右鍵單擊有時用於模仿Web應用程序中的上下文菜單。
圖書館支持WebDriver操作。通常,操作是描述虛擬輸入設備的命令。
{ :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 }]}]}
您可以手動創建地圖並將其發送到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)
或使用包裝紙。首先,您需要創建虛擬輸入設備,例如:
( def keyboard ( make-key-input ))
然後填寫必要的措施:
( -> keyboard
( add-key-down keys/shift-left)
( add-key-down " a " )
( add-key-up " a " )
( add-key-up keys/shift-left))
擴展示例:
( 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))
要清除虛擬輸入設備的狀態,請發布所有按鍵等,請使用release-actions
方法:
( release-actions driver)
單擊文件輸入按鈕打開一個特定於OS的對話框,您不允許使用WebDriver協議與之交互。使用upload-file
函數將本地文件附加到文件輸入小部件。該函數採用指向文件輸入的選擇器,而將完整路徑作為字符串或本機java.io.File
實例。該文件應該存在,否則您將獲得異常。用法示例:
( 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)
調用screenshot
函數將當前頁面轉儲到磁盤上的PNG圖像中:
( screenshot driver " page.png " ) ; ; relative path
( screenshot driver " /Users/ivan/page.png " ) ; ; absolute path
還支持本機Java文件對象:
; ; when imported as `[clojure.java.io :as io]`
( screenshot driver ( io/file " test.png " ))
; ; native object
( screenshot driver ( java.io.File. " test-native.png " ))
使用Firefox和Chrome,您可能不是捕獲整個頁面,而是一個單個元素,例如DIV,輸入小部件或其他內容。目前,它與其他瀏覽器無法使用。例子:
( screenshot-element driver { :tag :div :class :smart-widget } " smart_widget.png " )
使用with-screenshots
宏,您可以在每種形式之後製作屏幕截圖
( with-screenshots driver " ../screenshots "
( fill driver :simple-input " 1 " )
( fill driver :simple-input " 2 " )
( fill driver :simple-input " 3 " ))
等同於記錄:
( 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 " )
最近,Google Chrome和後來的Firefox啟動了支持名為Headless Mode的功能。當無頭時,屏幕上沒有任何UI窗口出現,只有Stdout輸出進入控制台。此功能使您可以在沒有圖形輸出設備的服務器上運行集成測試。
通過檢查是否接受--headles
從終端運行它時,請確保瀏覽器是否接受無頭模式。 Phantom.js驅動程序的性質無頭(從未用於渲染UI)。
啟動驅動程序時,通過:headless
布爾旗將切換到無頭模式。注意,僅支持最新版本的Chrome和Firefox。對於其他驅動程序,將忽略國旗。
( def driver ( chrome { :headless true })) ; ; runs headless Chrome
或者
( def driver ( firefox { :headless true })) ; ; runs headless Firefox
要檢查任何驅動程序已在無頭模式下運行,請使用headless?
謂詞:
( headless? driver) ; ; true
請注意,它將始終返回phantom.js實例。
默認情況下,有幾個可以在無頭模式下運行Chrome或Firefox的快捷方式:
( 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 " ))
當瀏覽器處於無頭模式或不分別時,也when-headless
when-not-headless
Macroses允許執行一堆命令:
( with-chrome nil driver
...
( when-not-headless driver
... some actions that might be not available in headless mode)
... common actions for both versions)
要連接到已經在本地或遠程主機上運行的驅動程序,您必須指定可能是主機名(Localhost,some.remote.host.net)或IP地址(127.0.0.1,183.102.156.31)的:host
)和:port
。如果未指定端口,則設置默認端口。
例子:
; ; 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
要與Docker中的駕駛員一起工作,您可以拍攝現成的圖像:
Chrome的示例:
docker run --name chromedriver -p 9515:4444 -d -e CHROMEDRIVER_WHITELISTED_IPS='' robcherry/docker-chromedriver:latest
對於Firefox:
docker run --name geckodriver -p 4444:4444 -d instrumentisto/geckodriver
要連接到驅動程序,您只需要指定:host
參數為localhost
或127.0.0.1
以及它正在運行的:port
。如果未指定端口,則設置默認端口。
( def driver ( chrome-headless { :host " localhost " :port 9515 :args [ " --no-sandbox " ]}))
( def driver ( firefox-headless { :host " localhost " })) ; ; will try to connect to port 4444
要設置代理設置使用環境變量HTTP_PROXY
/ HTTPS_PROXY
或傳遞以下類型的地圖:
{ :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 " }})
注意:答:PAC-url用於代理自動配置文件。與Safari一起使用,作為其他代理選項在該瀏覽器中不起作用。
為了微調代理,您可以使用原始對象並將其傳遞給功能:
{ :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 {...}}})
隨著最近的更新,該圖書館帶來了一個很棒的功能。現在,您可以追踪來自DevTools面板的事件。這意味著,您在開發人員控制台中看到的所有內容現在都可以通過API獲得。現在僅與Google Chrome一起使用。
要啟動具有指定特殊開發設置的驅動程序,只需將空的地圖傳遞到運行驅動程序時的:dev
字段:
( def c ( chrome { :dev {}}))
價值不能nil
。當它是一個空的地圖時,特殊功能會默認。這是開發設置的完整版本,並指定了所有可能的值。
( def c ( chrome { :dev
{ :perf
{ :level :all
:network? true
:page? true
:interval 1000
:categories [ :devtools
:devtools.network
:devtools.timeline ]}}}))
在引擎蓋下,它填充了chromeOptions
對像中的特殊perfLoggingPrefs
字典。
現在,您的瀏覽器會累積這些事件,您可以使用特殊的dev
名稱空間閱讀它們。
( go c " http://google.com " )
; ; wait until the page gets loaded
; ; load the namespace
( require '[etaoin.dev :as dev])
讓我們列出頁面加載期間發生的所有HTTP請求的列表。
( 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 }
由於我們對AJAX請求感興趣,因此有一個函數get-ajax
可以執行相同的操作,但過濾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 }
get-ajax
使用的典型模式是以下內容。您想檢查是否已向服務器啟動了某個請求。因此,您按下按鈕,等待一會兒,然後閱讀瀏覽器提出的請求。
有了一個請求列表,您可以搜索所需的請求(例如由其URL),然後檢查其狀態。 :state
Field具有與XMLHttpRequest.readyState
具有的相同語義。這是一個從1到4的整數,其行為相同。
要檢查請求是否已經完成,完成或失敗,請使用以下謂詞:
( def req ( last reqs))
( dev/request-done? req)
; ; true
( dev/request-failed? req)
; ; false
( dev/request-success? req)
; ; true
請注意該request-done?
並不意味著該請求已經成功。這僅意味著其管道已經達到最後一步。
警告:當您閱讀開發日誌時,您會從潮紅的內部緩衝區中消耗它們。第二個呼叫get-requests
或get-ajax
將返回一個空列表。
也許您想自己收集這些日誌。函數dev/get-performance-logs
返回一個日誌列表,因此您將其累積在原子或其他內容中:
( def logs ( atom []))
; ; repeat that form from time to time
( do ( swap! logs concat ( dev/get-performance-logs c))
true )
( count @logs)
; ; 76
有logs->requests
和logs->ajax
函數將日誌轉換為請求。與get-requests
和get-ajax
不同,它們是純粹的功能,不會沖洗任何東西。
( dev/logs->requests @logs)
使用日誌和請求時,請注意其計數和大小。這些地圖有很多鑰匙,收藏中的物品數量可能很大。打印一堆活動可能會凍結您的編輯器。考慮使用clojure.pprint/pprint
函數,因為它依賴於最大級別和長度限制。
有時,在上次UI測試會議期間發現出了問題可能很難。在觸發異常之前,特殊的宏with-postmortem
在磁盤上保存了一些有用的數據。這些數據是屏幕截圖,HTML代碼和JS控制台日誌。注意:並非所有瀏覽器都支持獲取JS日誌。
例子:
( def driver ( chrome ))
( with-postmortem driver { :dir " /Users/ivan/artifacts " }
( click driver :non-existing-element ))
一個例外將會增加,但是在/Users/ivan/artifacts
中,將有三個文件由模板<browser>-<host>-<port>-<datetime>.<ext>
:
firefox-127.0.0.1-4444-2017-03-26-02-45-07.png
:瀏覽器頁面的實際屏幕截圖;firefox-127.0.0.1-4444-2017-03-26-02-45-07.html
:當前瀏覽器的HTML內容;firefox-127.0.0.1-4444-2017-03-26-02-45-07.json
:帶有控制台日誌的JSON文件;這些是對象的向量。處理程序用以下鍵拍攝選項地圖。他們所有人都可能不存在。
{ ; ; 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 " }
功能(get-logs driver)
作為地圖向量返回瀏覽器的日誌。每個地圖都有以下結構:
{ :level :warning ,
:message " 1,2,3,4 anonymous (:1) " ,
:timestamp 1511449388366 ,
:source nil,
:datetime #inst " 2017-11-23T15:03:08.366-00:00 " }
當前,日誌僅在Chrome和Phantom.js中可用。請注意,消息文本和源類型高度取決於瀏覽器。鍍鉻一旦閱讀了日誌。 phantom.js保留它們,但僅在更改頁面之前。
運行驅動程序實例時,可能會傳遞其他參數的地圖以調整瀏覽器的行為:
( def driver ( chrome { :path " /path/to/driver/binary " }))
下面,這是庫支持的參數地圖。所有這些都可能被跳過或具有零值。其中一些(如果未通過)取自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 " ]}}}
當您導航到某個頁面時,驅動程序會等到整個頁面完全加載。在大多數情況下,還不錯的情況並不能反映人類與互聯網互動的方式。
通過以下方式更改此默認行為:load-strategy
選項。為此,有三個可能的值:: :none
, :eager
和:normal
,這是未傳遞時默認值。
當您通過時:none
,駕駛員會立即響應,因此歡迎您執行下一個說明。例如:
( def c ( chrome ))
( go c " http://some.slow.site.com " )
; ; you'll hang on this line until the page loads
( do-something )
現在,通過加載策略選項:
( def c ( chrome { :load-strategy :none }))
( go c " http://some.slow.site.com " )
; ; no pause, acts immediately
( do-something )
對於:eager
選項,它僅在將功能添加到庫中時才與Firefox一起使用。
可以選擇同時輸入一系列鍵。這對於模仿持有系統鍵(例如控制,移動或鍵入時)很有用。
命名空間etaoin.keys
攜帶一堆關鍵常數以及與輸入相關的一組功能。
( require '[etaoin.keys :as keys])
輸入普通字符的快速示例:
( def c ( chrome ))
( go c " http://google.com " )
( fill-active c ( keys/with-shift " caps is great " ))
主要輸入填充了“帽子很棒”。現在您想刪除最後一個單詞。在Chrome中,這是通過按下BackSpace Holding Alt來完成的。讓我們這樣做:
( fill-active c ( keys/with-alt keys/backspace))
現在,您只有“帽子”在輸入中。
考慮一個更複雜的示例,該示例重複真實用戶的行為。您想從輸入中刪除所有內容。首先,您從一開始就移動商人。然後將其移動到末端握持偏移,以便所有內容都可以選擇。最後,您按DELETE清除所選文本。
組合是:
( fill-active c keys/home ( keys/with-shift keys/end) keys/delete)
也有with-ctrl
和with-command
函數的作用相同。
請注意,這些功能不適用於全球瀏覽器的快捷方式。例如,“命令 + r”和“命令 + t”都不重新加載頁面或打開新選項卡。
所有keys/with-*
只是包裝器上的keys/chord
函數,可用於復雜情況。
要指定您自己的目錄在哪裡下載文件,請通過:download-dir
到選項映射中:
( def driver ( chrome { :download-dir " /Users/ivan/Desktop " }))
現在,單擊鏈接後,應將文件放入該文件夾中。目前,僅支持Chrome和Firefox。
Firefox需要指定應該下載的這些文件的MIME類型,而不會顯示系統對話框。默認情況下,當傳遞:download-dir
參數時,庫添加了最常見的Mime類型:檔案,媒體文件,辦公室文檔等。如果您需要添加自己的內容,請手動覆蓋該偏好:
( def driver ( firefox { :download-dir " /Users/ivan/Desktop "
:prefs { :browser.helperApps.neverAsk.saveToDisk
" some-mime/type-1;other-mime/type-2 " }}))
要檢查在UI測試期間是否下載文件,請參見下面的測試部分。
例如,在創建驅動程序時,設置一個自定義的用戶代理標頭,則使用:user-agent
選項:
( def f ( firefox { :user-agent " Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1) " }))
要在運行時獲得標頭的當前值,請使用該函數:
( get-user-agent f)
; ; Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)
設置該標頭對於無頭瀏覽器非常重要,因為大多數站點檢查用戶代理是否包含“無頭”字符串。這可能會導致403個響應或網站的某些怪異行為。
運行Chrome或Firefox時,您可以指定用於測試目的的特殊配置文件。配置文件是一個文件夾,可保留瀏覽器設置,歷史記錄,書籤和其他特定於用戶的數據。
想像一下,您想對關閉JavaScript執行或圖像渲染的用戶進行集成測試。為該任務準備一個特殊的個人資料將是一個不錯的選擇。
chrome://version/
頁面。複製Profile Path
標題下方的文件路徑。-P
, -p
或-ProfileManager
鍵運行firefox。about:support
頁面。在Profile Folder
標題附近,按Show in Finder
。應該出現一個新的文件夾窗口。從那裡複製其路徑。有了配置文件路徑後,請啟動帶有特殊驅動程序的驅動程序:profile
鍵,如下:
; ; 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}))
該庫寄出了一組功能以滾動頁面。
最重要的scroll-query
拼寫是通過查詢術語找到的第一個元素:
( def driver ( chrome ))
; ; the form button placed somewhere below
( scroll-query driver :button-submit )
; ; the main article
( scroll-query driver { :tag :h1 })
要跳到絕對位置,只需使用以下方式使用scroll
:
( scroll driver 100 600 )
; ; or pass a map with x and y keys
( scroll driver { :x 100 :y 600 })
要相對滾動,請使用帶有偏移值的scroll-by
:
; ; keeps the same horizontal position, goes up for 100 pixels
( scroll-by driver 0 -100 ) ; ; map parameter is also supported
有兩個快捷方式可以跳到頁面的頂部或底部:
( scroll-bottom driver) ; ; you'll see the footer...
( scroll-top driver) ; ; ...and the header again
以下功能在各個方向上滾動頁面:
( 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)
一個值得注意的是,在所有情況下,滾動操作都與JavaScript一起服務。確保您的瀏覽器已啟用。
在與頁面合作時,您無法與放入框架或iFrame的項目進行交互。下面的功能在特定幀上切換當前上下文:
( 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
框架可以嵌套在另一個。這些功能考慮到這一點。假設您有這樣的HTML佈局:
< iframe src =" ... " >
< iframe src =" ... " >
< button id =" the-goal " >
</ frame >
</ frame >
因此,您可以使用以下代碼到達按鈕:
( 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
為了減少代碼線的數量,有一個特殊的with-frame
宏。它暫時切換框架,同時執行身體返回其最後一個表達式並之後切換到上一個幀。
( with-frame driver { :id :first-frame }
( with-frame driver { :id :nested-frame }
( click driver { :id :nested-button })
42 ))
上面的代碼返回42
固定在評估宏之前的框架上。
要在瀏覽器中評估JavaScript代碼,請運行:
( js-execute driver " alert(1) " )
您可以將任何其他參數傳遞到呼叫中,並在腳本中使用類似於arguments
數組的對象的腳本將它們轉到腳本中:
( js-execute driver " alert(arguments[2].foo) " 1 false { :foo " hello! " })
結果, hello!
字符串將顯示在對話框內。
要將任何數據返回到clojure中,只需添加return
到您的腳本:
( js-execute driver " return {foo: arguments[2].foo, bar: [1, 2, 3]} "
1 false { :foo " hello! " })
; ; {:bar [1 2 3], :foo "hello!"}
如果您的腳本執行AJAX請求或在setTimeout
或任何其他異步內容上執行或操作,則不能僅return
結果。相反,應針對您要獲得的數據進行特殊的回調。 WebDriver將此回調作為您的腳本的最後一個參數傳遞,並且可以使用類似於Array的對象來達到arguments
。
例子:
( 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 }}})
將42
返回到Clojure代碼。
要評估異步腳本,您需要為此設置一個特殊的超時:
( set-script-timeout driver 5 ) ; ; in seconds
或將代碼包裹到暫時的宏中:
( with-script-timeout driver 30
( js-async driver " some long script " ))
程序和人類之間的主要區別在於,第一個運行速度非常快。這意味著如此之快,以至於有時瀏覽器無法及時渲染新的HTML。因此,在每次操作之後,您最好將wait-<something>
函數進行輪詢,直到謂詞評估為true為止。或僅(wait <seconds>)
,如果您不關心優化。
當您需要使用(wait n)
進行每個操作時with-wait
宏可能會有所幫助。例如,以下形式
( with-chrome {} driver
( with-wait 3
( go driver " http://site.com " )
( click driver { :id " search_button " })))
變成這樣的東西:
( with-chrome {} driver
( wait 3 )
( go driver " http://site.com " )
( wait 3 )
( click driver { :id " search_button " }))
因此返回原始身體的最後形式的結果。
還有另一個宏(doto-wait n driver & body)
,它的作用類似於標準doto
但用(wait n)
預先準備每種表格。例如:
( with-chrome {} driver
( doto-wait 1 driver
( go " http://site.com " )
( click :this-link )
( click :that-button )
...etc))
最終形式將是這樣的:
( with-chrome {} driver
( doto driver
( wait 1 )
( go " http://site.com " )
( wait 1 )
( click :this-link )
( wait 1 )
( click :that-button )
...etc))
除了with-wait
和do-wait
還有許多等待功能: wait-visible
, wait-has-alert
, wait-predicate
等(請參閱“相關”部分中的完整列表)。他們接受默認超時/間隔值,可以分別使用with-wait-timeout
和with-wait-interval
宏來重新定義。
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 " ))))
等待文字:
wait-has-text
等待,直到元素內部的任何地方都有文本(包括內部HTML)。
( wait-has-text driver :wait-span " -secret- " )
wait-has-text-everywhere
就像wait-has-text
一樣,但在整個頁面上搜索文本
( wait-has-text-everywhere driver " -secret- " )
為了使您的測試不取決於彼此,您需要將它們包裹在一個固定裝置中,該固定裝置將創建驅動程序的新實例,並在每次測試時正確將其正確關閉。
好的解決方案可能是擁有一個全局變量(默認情況下),該變量將指向測試過程中的目標驅動程序。
( 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 )
...
))
如果由於某種原因您想使用一個實例,則可以使用類似的固定裝置:
( 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
對於更快的測試,您可以使用此示例:
.....
( 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 ))
......
在上面的示例中,我們檢查了一種針對單一類型驅動程序進行測試時的情況。但是,您可能需要在多個驅動程序(例如Chrome和Firefox)上測試您的網站。在這種情況下,您的固定裝置可能會變得更加複雜:
( 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 ))))))
現在,每個測試將在Firefox和Chrome瀏覽器中進行兩次。請注意,測試調用是用testing
驅動程序名稱放入報告的宏觀上的。一旦遇到錯誤,您就可以輕鬆地發現驅動程序完全失敗了測試。
為了保存一些例外的文物,將測試的正文包裹在with-postmortem
處理程序中,如下所示:
( 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...
)))
現在,如果在該測試中發生任何例外,將保存工件。
要不復制和粘貼選項映射,請在模塊的頂部聲明。如果您使用Circle CI,將數據保存到特殊的文物目錄中將是很棒的,該目錄一旦構建完成後,該數據可能會以郵政為文件下載:
( def pm-dir
( or ( System/getenv " CIRCLE_ARTIFACTS " ) ; ; you are on CI
" /some/local/path " )) ; ; local machine
( def pm-opt
{ :dir pm-dir})
現在將那個地圖傳遞到PM處理程序中:
; ; test declaration
( with-postmortem *driver* pm-opt
; ; test body goes here
)
發生錯誤後,您將找到一個在異常和HTML轉儲時代表您的瀏覽器頁面的PNG圖像。
由於UI測試可能需要花費大量時間來通過,因此彼此獨立通過服務器和UI測試絕對是一個好習慣。
首先,添加^:integration
標籤到所有運行的測試中,如下所示:
( deftest ^:integration
test-password-reset-pipeline
( doto *driver*
( go url-password-reset)
( click :reset-btn )
...
然後,打開您的project.clj
文件並添加測試選擇器:
:test-selectors { :default ( complement :integration )
:integration :integration }
現在,一旦啟動lein test
您將運行除瀏覽器以外的所有測試。要運行集成測試,請啟動lein test :integration
。
程序和人類之間的主要區別在於,第一個操作非常快。這意味著如此之快,以至於有時瀏覽器無法及時渲染新的HTML。因此,在每次操作之後,您需要放置wait-<something>
函數,該功能僅輪詢瀏覽器檢查謂詞。 o (wait <seconds>)
,如果您不在乎優化。
有時,一旦您單擊鏈接或訪問了某些頁面,文件就會自動下載。在測試中,您需要確保已成功下載文件。一個常見的情況是:
例子:
; ; 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可以播放硒IDE產生的文件。這是一種官方實用程序,可以交互創建方案。 IDE是您瀏覽器的擴展。安裝後,它將您的操作記錄到帶有.side
擴展程序的JSON文件中。您可以保存該文件並使用Etaoin運行。
讓我們想像您已經安裝了IDE,並記錄了一些官方文檔規定的動作。現在您已經有了一個test.side
。
( 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)
與IDE相關的所有內容都存儲在etaoin.ide
軟件包下。
您也可以從命令行運行腳本。這是lein run
示例:
lein run -m etaoin.ide.main -d firefox -p ' {:port 8888 :args ["--no-sandbox"]} ' -r ide/test.side
以及來自Uberjar的。在這種情況下,etaoin必須處於主要依賴性中,而不是:dev
或:test
相關。
java -cp .../poject.jar -m etaoin.ide.main -d firefox -p ' {:port 8888} ' -f ide/test.side
我們支持以下參數(使用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
注意--params
一家。這必須是代表Clojure地圖的EDN字符串。那是您在啟動驅動程序時傳遞到驅動程序的地圖。
請注意,IDE支持仍然是實驗性的。如果您遇到意外行為,請隨時打開問題。目前,我們僅支持Chrome和Firefox用於IDE文件。
例子:
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) " }},
...
解決方案:只需將chromedriver
更新到最後一個版本即可。用2.29測試,正常工作。人們說,自2.28起,它也會炒作。
請記住, brew
Package Manager擁有過時的2.27版。您可能必須從官方網站下載二進製文件。
請參閱Selenium項目中的相關問題。
傳遞類似矢量的查詢時,說[{:tag :p} "//*[text()='foo')]]"}]
。在矢量中,其每個表達式都會從上一個循環中搜索。這裡有一個隱藏的錯誤:沒有領先點, "//..."
子句意味著從整個頁面的根部找到一個元素。使用一個點,這意味著從當前節點中找到是從上一個查詢等節點等。
這就是為什麼,很容易選擇完全不同的東西。適當的表達方式是: [{:tag :p} ".//*[text()='foo')]]"}]
。
例子:
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) " }},
...
解決方案:您正在嘗試單擊一個不可見的元素,或者它的佈置卻少了,因為人類不可能單擊它。您應該通過另一個選擇器。
問題:當您專注於其他窗口時,在Google Chrome下運行的WebDriver會話失敗。
解決方案: Google Chrome在一段時間內無效時可能會暫停選項卡。當該頁面被暫停時,無法對其進行操作。無點擊,JS執行等。因此,請嘗試在測試會話期間保持Chrome窗口活動。
問題:當您嘗試啟動驅動程序時,您會遇到錯誤:
user=> ( use 'etaoin.api)
user=> ( def driver ( firefox { :headless true }))
語法錯誤(exceptionInfo)在(repl:1:13)上編譯。 throw+:{:wendesp {:value {:錯誤“未知錯誤”,:message“無效參數:不能殺死退出的過程” ...
可能的原因:“不支持常規用戶會話中的Firefox作為root,”
解決方案:要檢查,請運行帶有日誌文件路徑和“跟踪”日誌級別的驅動程序,然後探索其輸出。
( def driver ( firefox { :log-stdout " ffout.log " :log-stderr " fferr.log " :driver-log-level " trace " }))
類似的問題:Mozilla/Geckodriver#1655
問題:當您嘗試啟動Chromedriver時,您會遇到錯誤:
clojure.lang.exceptionInfo:throw+:{:wendesp {:sessionid“ .....文件不存在)...
可能的原因:
在啟動期間,Chrome崩潰的一個常見原因是在Linux上運行Chrome作為root用戶(管理員)。雖然可以通過傳遞-NO-Sandbox標誌在創建WebDriver會話時解決此問題,但是這種配置是不支持的,並且高度灰心。您需要配置環境以將Chrome作為常規用戶運行。
解決方案:帶有參數的驅動程序--no-sandbox
。警告!這是一個旁路操作系統安全模型。
( def driver ( chrome { :args [ " --no-sandbox " ]}))
這裡描述了類似的問題
etaoin.api2
命名空間帶來了一些替代宏和功能。他們提供更好的語法,並在單獨的名稱空間中生活,以防止舊的API破裂。
目前, api2
模塊提供了一組with-...
宏,帶有let
-like綁定形式:
( ns ...
( :require
[etaoin.api :as api]
[etaoin.api2 :as api2]))
( api2/with-chrome [driver {}]
( api/go driver " http://ya.ru " ))
可以跳過選項映射,因此您只有一個綁定符號:
( api2/with-firefox [ff]
( api/go ff " http://ya.ru " ))
該項目為您的改進和想法開放。如果您的機器上的任何單元測試都屬於您的機器,請提交問題,瀏覽器和控制台輸出。
版權所有©2017-2020 Ivan Grishaev。
根據Eclipse公共許可分發行1.0版或(根據您的選項)任何以後的版本。