15

Web自動化テストにpython seleniumを使用しています。自動化の重要な部分は、HTML ページでユーザーに表示されるオブジェクトの適切な要素を見つけることです。次の API はほとんどの場合に機能しますが、常に機能するとは限りません。

find_element_by_xxx,  xxx can be id, name, xpath, tag_name etc. 

HTMLページが複雑すぎる場合、domツリーを検索したい。SeleniumサーバーにDOM全体をシリアライズするように依頼することが可能かどうか疑問に思います(webdriverサーバーを介してアクションを実行するために使用できる要素IDを使用)。クライアント側 (python スクリプト) は独自の検索アルゴリズムを実行して、適切な要素を見つけることができます。

python selenium は、html ページ全体を取得できることに注意してください。

drv.page_source

ただし、これを解析しても、セレンサーバーの観点からは内部要素 ID が得られないため、役に立ちません。

EDIT1 : より明確にするために言い換えます(@alecxeに感謝します):ここで必要なのは、セレンサーバー内のすべてのDOM要素(DOM構造が保持された状態)のシリアル化された表現です。このシリアル化された表現はクライアント側に送信できます(独自の検索を実行できる Python Selenium テスト アプリ)。

4

5 に答える 5

20

問題

サーバー (ブラウザー) 側ではなく、クライアント (Python) 側でページの実質的な処理を実行する必要がある場合があります。たとえば、何らかの機械学習システムが既に Python で記述されていて、アクションを実行する前にページ全体を分析する必要がある場合、一連のfind_element呼び出しでそれを実行することは可能ですが、それぞれが非常に高価になるため、非常にコストがかかります。呼び出しは、クライアントとサーバー間の往復です。また、ブラウザで動作するように書き直すと、コストがかかりすぎる可能性があります。

Selenium の識別子がそうしない理由

ただし、 Selenium 独自の識別子と一緒に DOM をシリアル化する効率的な方法がわかりません。Selenium は、呼び出し時、または呼び出しから DOM ノードが返される(またはスクリプトに与えるコールバックに渡される) ときに、必要に応じてこれらの識別子を作成します。しかし、各要素の識別子を取得するために呼び出すと、振り出しに戻ります。ブラウザーで必要な情報を使用して DOM を装飾することは想像できますが、なんらかの事前割り当てを要求するパブリック API はありません。find_elementexecute_scriptexecute_async_scriptfind_elementWebElementID。実際のところ、これらの識別子は不透明になるように設計されているため、ソリューションが必要な情報を取得するために何らかの方法で管理されたとしても、クロスブラウザーの実行可能性と継続的なサポートが懸念されます.

解決策

ただし、両側で機能するアドレス指定システムを取得する方法があります: XPath. クライアント側で DOM シリアライゼーションをツリーに解析し、関心のあるノードの XPath を取得し、これを使用して対応する WebElement を取得するという考え方です。find_elementしたがって、クリックを実行する必要がある単一の要素を決定するために何十回ものクライアント サーバー ラウンドトリップを実行する必要がある場合は、これをページ ソースの最初のクエリとXPath を使用した単一の呼び出しに減らすことができます。あなたが必要です。

これは非常に単純な概念実証です。Google フロント ページのメイン入力フィールドをフェッチします。

from StringIO import StringIO

from selenium import webdriver
import lxml.etree

#
# Make sure that your chromedriver is in your PATH, and use the following line...
#
driver = webdriver.Chrome()
#
# ... or, you can put the path inside the call like this:
# driver = webdriver.Chrome("/path/to/chromedriver")
#

parser = lxml.etree.HTMLParser()

driver.get("http://google.com")

# We get this element only for the sake of illustration, for the tests later.
input_from_find = driver.find_element_by_id("gbqfq")
input_from_find.send_keys("foo")

html = driver.execute_script("return document.documentElement.outerHTML")
tree = lxml.etree.parse(StringIO(html), parser)

# Find our element in the tree.
field = tree.find("//*[@id='gbqfq']")
# Get the XPath that will uniquely select it.
path = tree.getpath(field)

# Use the XPath to get the element from the browser.
input_from_xpath = driver.find_element_by_xpath(path)

print "Equal?", input_from_xpath == input_from_find
# In JavaScript we would not call ``getAttribute`` but Selenium treats
# a query on the ``value`` attribute as special, so this works.
print "Value:", input_from_xpath.get_attribute("value")

driver.quit()

ノート:

  1. driver.page_sourceSelenium のドキュメントには、返される内容の鮮度について保証がないと記載されているため、上記のコードは使用しません。現在の DOM の状態、またはページが最初に読み込まれたときの DOM の状態である可能性があります。

  2. find_elementこの解決策には、動的コンテンツに関する問題とまったく同じ問題があります。分析の実行中に DOM が変更された場合、DOM の古い表現で作業しています。

  3. 分析の実行中に JavaScript イベントを生成する必要があり、これらのイベントによって DOM が変更される場合は、DOM を再度取得する必要があります。(これは前のポイントと似ていますが、呼び出しを使用するソリューションは、呼び出しのシーケンスを慎重に並べることによって、このfind_elementポイントで話している問題をおそらく回避できます。)

  4. lxmlのツリーは、 から取得した XPathが DOM 内の対応する要素をアドレス指定しないという点で、DOM ツリーとは構造的に異なる可能性があります。ブラウザーに渡された HTML のクリーンアップされたシリアル化されたビューとはlxmlどのようなプロセスですか。lxmlしたがって、ポイント 2 と 3 で述べた問題を防ぐようにコードが書かれている限り、これはありそうなシナリオではないと思いますが、不可能ではありません。

于 2014-09-03T14:03:15.437 に答える
16

試す:

find_elements_by_xpath("//*")

これは、ドキュメント内のすべての要素と一致する必要があります。

更新 (質問の絞り込みに合わせて):

JavaScript を使用して、DOM を文字列として返します。

execute_script("return document.documentElement.outerHTML")
于 2014-08-31T14:27:56.383 に答える
2

Selenium の識別子を取得しようとする試みに関する問題については、私の他の回答を参照してください。

繰り返しになりますが、問題は一連の呼び出しを減らして、find_elementそれらに関連するラウンドトリップを回避することです。

私の他の答えとは異なる方法は、ブラウザで検索execute_scriptを実行し、必要なすべての要素を返すために使用することです。たとえば、このコードでは 3 回の往復が必要ですが、1 回の往復に減らすことができます。

el, parent, text = driver.execute_script("""
var el = document.querySelector(arguments[0]);
return [el, el.parentNode, el.textContent];
""", selector)

これは、渡したい CSS セレクターに基づいて、要素、要素の親、および要素のテキスト コンテンツを返します。ページに jQuery が読み込まれている場合は、jQuery を使用して検索を実行できます。また、ロジックは必要に応じて複雑になる可能性があります。

この方法は、往復を減らすことが望ましい大多数のケースを処理しますが、他の回答の図で示したようなシナリオは処理しません。

于 2014-09-03T14:18:52.293 に答える
0

ページ オブジェクト パターンを利用してみることができます。この場合、それはあなたが探しているものに近いように聞こえます。すべてをそれに変更するわけではないかもしれませんが、少なくともこの部分については、それを考慮する必要があるかもしれません.

http://selenium-python.readthedocs.org/en/latest/test-design.html?highlight=page%20オブジェクト

ページのすべての要素をループして、一度に 1 つずつ保存することもできますが、それができるライブラリが必要です。.Net には htmlAgility があることを知っています。Pythonについてはわかりません。

更新 これを見つけました...おそらく役立つでしょう。 Python 用 HTML アジリティ パック

于 2014-08-18T14:03:31.853 に答える
-1

実際、これは非常に簡単に行うことができます。出力を次のようにストリームに書き込みvar w = window.open...、次にdocument.write...

各オブジェクトを返す JSON.Stringify を返すドキュメント オブジェクトを再帰的に反復します。投げ込むこともお勧めしますtypeof

var s = 
recurse(obj) {
    for(var i in obj) {
       return typeof(i) + ":" + i.toString() + ":" + JSON.stringify(obj[i]);
    }
}

表示したくないプロパティを削除するために、何らかのフィルタリングを追加することをお勧めします。また、ブラウザが再帰ループを検出してエスケープするときに実行されるとは思えません。

私は似たようなものを探しているこの質問を見つけましたが、クロムよりも優れた何らかのデバッグウィンドウにバインドできる DataTable オブジェクト (.Net を使用しています) を望んでいました。これを行うためにfirebugを使用する前に、それはちょっと死んでいます。

したがって、このデータを取得することもできますが、デバッガーを使用してリアルタイムで取得できます。

于 2017-02-24T09:16:58.590 に答える