WebDriverプロトコルの純粋なClojure実装。そのライブラリを使用して、ブラウザを自動化し、フロントエンドの動作をテストし、人間の行動や必要なものをシミュレートします。
Etaoin Shrdluにちなんで名付けられました。これは、ミステリーメモが生成された後に生き生きとしたタイピングマシンです。
0.4.0
以降、ドライバーインスタンスはマップですが、以前のような原子ではありません。決定するのは難しい解決策でしたが、コードのClojure Wayに従うためのAtomを取り除いています。一般的に言えば、あなたはドライバーをderefしたり、その中に何かを保管したりしません。インスタンスを変更するために使用されるすべての内部関数は、新しいバージョンのマップを返すだけです。 swap!
または、ドライバーのコードに似たものがありますが、更新する前にコードをリファクタリングしてください。
0.4.0
以降、ライブラリはWebDriverアクションをサポートしています。アクションは、バッチのドライバーに送信されるコマンドです。 TOCの詳細な関連セクションを参照してください。
0.4.0
以降、エタオインはインタラクティブセレンIDEで作成されたスクリプトファイルを再生できます。以下の関連セクションを参照してください。
Ct t
を押すEMACSからユニットテストを直接実行します。あなたの会社をそのリストに提出することを歓迎します。
インストールには2つの手順があります。
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
Macrosを使用して単純化することができます。
( 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
キーは、Trailing [x]
句に拡大します。たとえば、テーブルから3番目の行を選択する必要がある場合に便利です。:fn/
namespaceがあり、特定のものに拡大します。例:
最初のdiv
タグを見つけます
( query driver { :tag :div })
; ; expands into .//div
n番目の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"]
「ダウンロード」テキストでnth要素( p
、 a
、何でも)を見つけます:
( 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'] " ])
たとえば、この例の2番目のリンクをクリックしたい場合、クエリの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 }])
または、次のようなCSS式でnth-childトリックを使用することもできます。
( click driver { :css " li.search-result:nth-child(2) a " })
最後にquery-all
を使用して、 n番目の要素を直接取得することもできます。
( click-el driver ( nth ( query-all driver { :css " li.search-result a " }) 2 ))
query-all
要素を返しているので、ここでclick-el
の使用に注意してくださいclick
query-tree
セレクターを取り、木のように動作します。次のすべてのセレクターは、以前の要素の要素をクエリします。拳セレクターは発見要素に依存し、残りのセレクターはfind-elementsからの使用を使用します
( 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
関数は、クエリで見つかった最初の要素のみを使用します。これにより、間違ったアイテムをクリックすることもあります。 1つの要素が1つしか見つかっていないことを確認するには、 click-single
関数を使用します。同じように動作しますが、ページを照会すると複数の要素を返すと例外が発生します。
( click-single driver { :tag :button :name " search " })
ダブルクリックはWebでめったに使用されませんが、 double-click
関数(Chrome、Phantom.js)では可能です。
( 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)
[ファイル入力]ボタンをクリックすると、WebDriverプロトコルを使用して対話することが許可されていないOS固有のダイアログが開きます。 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インスタンスのためにtrueを返します。
デフォルトでヘッドレスモードで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
マクロスがあります。
( with-chrome nil driver
...
( when-not-headless driver
... some actions that might be not available in headless mode)
... common actions for both versions)
既にローカルまたはリモートのホストで実行されているドライバーに接続するには、次の指定する必要があります:host
パラメーターは、ホスト名(localhost、some.remote.host.net)またはIPアドレス(127.0.0.1、183.102.156.31である可能性があります。 )および: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 " }})
注:A:プロキシAutoconfigurationファイルの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
あってはなりません。空のマップの場合、特別な関数にはデフォルトが必要です。これは、すべての可能な値が指定されたDEV設定のフルバージョンです。
( 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リクエストに興味があるので、同じことを行うがXHRリクエストをフィルタリングする関数get-ajax
があります。
( -> 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など)を検索し、その状態を確認します。 The :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?
リクエストが成功したという意味ではありません。それは、そのパイプラインが最終的なステップに到達したことを意味します。
警告: Devログを読むと、フラッシュされる内部バッファーからそれらを消費します。 get-requests
またはget-ajax
2回目の呼び出しは、空のリストを返します。
おそらく、あなたはあなた自身でこれらのログを収集したいのです。 function 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 " }
function (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のみで利用できます。メッセージテキストとソースタイプは、ブラウザに大きく依存しています。 Chromeが読み取られたら、ログを拭きます。 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
オプション。そのためには3つの可能な値があります。 :none
、 :eager
and :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では、これはAltを保持するBackspaceを押すことによって行われます。それをしましょう:
( fill-active c ( keys/with-alt keys/backspace))
これで、入力に「キャップは」だけです。
実際のユーザーの動作を繰り返すより複雑な例を考えてみましょう。入力からすべてを削除したいと思います。まず、最初にカレットを動かします。次に、すべてが選択されるようにシフトを最後に移動します。最後に、削除を押して選択したテキストをクリアします。
コンボは次のとおりです。
( 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
ページの上または下部をジャンプする2つのショートカットがあります。
( 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)
1つの注意事項、すべての場合において、スクロールアクションは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は、このコールバックをスクリプトの最後の引数として渡し、 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を時間内にレンダリングできない場合があります。したがって、各アクションの後、述語がtrueに評価されるまでブラウザを投票するだけのwait-<something>
関数を配置する方が良いでしょう。または、最適化を気にしない場合は(wait <seconds>)
。
with-wait
マクロは、各アクションを(wait n)
で準備する必要がある場合に役立つ場合があります。たとえば、次のフォーム
( 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)
でprepedingする別のマクロ(doto-wait n driver & body)
があります。例えば:
( 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
などがあります(Corresponsingセクションの完全なリストを参照)。彼らは、それぞれwith-wait-timeout
およびwith-wait-interval
マクロを使用して再定義できるデフォルトのタイムアウト/間隔値を受け入れます。
エタオインテストの例:
( 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ブラウザーの両方で2回実行されます。テストコールは、ドライバー名をレポートに入れる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を使用する場合、ビルドが終了したらZIPファイルとしてダウンロードされる可能性のある特別なArtifactsディレクトリにデータを保存することは素晴らしいことです。
( 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
)
エラーが発生すると、例外の時点でブラウザページを表すPNG画像とHTMLダンプがあります。
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は、Selenium IDEによって生成されたファイルを再生できます。シナリオをインタラクティブに作成するのは公式のユーティリティです。 IDEは、ブラウザの拡張機能として提供されます。インストールすると、 .side
拡張機能を備えたJSONファイルにアクションを記録します。そのファイルを保存して、エタオインで実行できます。
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からだけでなく。この場合、エタオインは: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サポートはまだ実験的です。予期しない動作に遭遇した場合は、お気軽に問題を開いてください。現時点では、IDEファイルのChromeとFirefoxのみをサポートしています。
例:
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 Projectの関連する問題を参照してください。
ベクトルのようなクエリを渡すときは、 [{:tag :p} "//*[text()='foo')]]"}]
をたとえば、手書きのxpath式に注意してください。ベクターでは、そのすべての式がループ内の前のものから検索します。ここには隠された間違いがあります。先頭のドットがなければ、 "//..."
句は、ページ全体のルートから要素を見つけることを意味します。ドットを使用すると、現在のノードから見つけることを意味します。これは、前のクエリなどです。
だから、あなたが望むものとはまったく違うものを選択するのは簡単です。適切な式は[{: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+:{:response {:value {:エラー "不明エラー"、:メッセージ "無効な引数:出口プロセスを殺すことはできません" ....
考えられる原因:「通常のユーザーのセッションでルートとしてFirefoxを実行することはサポートされていません」
解決策:チェックするには、ログファイルと「トレース」ログレベルへのパスでドライバーを実行し、出力を検討します。
( def driver ( firefox { :log-stdout " ffout.log " :log-stderr " fferr.log " :driver-log-level " trace " }))
同様の問題:Mozilla/Geckodriver#1655
問題: Chromedriverを起動しようとすると、エラーが発生します。
clojure.lang.exceptionInfo:shrow+:{:responsed {:sessionId "....."、:status 13、:value {:message "unknown error:chromeが起動しなかった:異常に終了しました。ファイルは存在しません)...
考えられる原因:
起動中にChromeがクラッシュする一般的な原因は、Linuxでルートユーザー(管理者)としてChromeを実行することです。 WebDriverセッションを作成するときに-No-Sandboxフラグを渡すことでこの問題を回避することは可能ですが、そのような構成はサポートされておらず、非常に落胆します。 You need to configure your environment to run Chrome as a regular user instead.
Solution: Run driver with an argument --no-sandbox
.注意! 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.