web-dev-qa-db-ja.com

python-Selenium-webdriver'quit 'が終了しないのはなぜですか?

非常に複雑なpy.test python-Seleniumテストセットアップがあり、py.testフィクスチャ内にFirefoxWebドライバーを作成します。これが私がしていることのいくつかの考えです:

'driver.py':

class Driver(object):
    """
    Driver class with basic wrappers around the Selenium webdriver
    and other convenience methods.
    """
    def __init__(self, config, options):
        """Sets the driver and the config.
        """

        self.remote = options.getoption("--remote")
        self.headless = not options.getoption("--with-head")    
        if self.headless:
            self.display = Display(visible=0, size=(13660, 7680))
            self.display.start()

        # Start the Selenium webdriver
        self.webdriver = fixefox_module.get_driver()

'conftest.py':

@pytest.fixture
def basedriver(config, options):

    driver = driver.Driver(config, options)

    yield driver

    print("Debug 1")
    driver.webdriver.quit()
    print("Debug 2")

そして、テストを実行すると、Debug 1が印刷されただけで表示されます。プロセス全体がこの時点で停止し、進行していないようです。 Seleniumテスト全体がwebdriver.quit)でスタックしています。

ただし、テストは正常に完了しました...

その振る舞いにはどのような理由が考えられますか?

補遺:

実行がハングする理由は、データが保存されていないためにページを離れるかどうかをユーザーに尋ねるポップアップのようです。これは、quitメソッドのドキュメントが正しくないことを意味します。それは述べています:

Quits the driver and close every associated window.
9
Alex

これは重要な問題であり、Seleniumの動作は実際には一貫性がありません。 quitメソッドは、文書化されているように、ブラウザウィンドウを閉じるだけですが、閉じません。代わりに、ユーザーにページを離れるかどうかを尋ねるポップアップが表示されます。

enter image description here

厄介なのは、このポップアップはユーザーが電話をかけた後にのみ表示されることです

driver.quit()

これを修正する1つの方法は、ドライバーに次のプロファイルを設定することです。

from Selenium import webdriver
profile = webdriver.FirefoxProfile()
# other settings here
profile.set_preference("dom.disable_beforeunload", True)
driver = webdriver.Firefox(firefox_profile = profile)
6
Alex

about:configに表示されているように、Firefoxではデフォルトで閉じる警告がtrueになっており、プロファイルで無効にすることができます。

Firefox config

それ以来、

実行がハングする理由は、データが保存されていないためにページを離れるかどうかをユーザーに尋ねるポップアップのようです。

Firefox構成プロファイルでbrowser.tabs.warnOnCloseを次のように設定できます。

from Selenium import webdriver

profile = webdriver.FirefoxProfile()
profile.set_preference("browser.tabs.warnOnClose", False)
driver = webdriver.Firefox(firefox_profile = profile)

profile.DEFAULT_PREFERENCESのjsonであるpython/site-packages/Selenium/webdriver/firefox/webdriver_prefs.jsonを見ることができます

Default Preferences

3
abybaddi009

だから私は以下のサンプルHTMLファイルを使用してあなたの問題を再現することができました

<html>
<body>
    Please enter a value for me: <input name="name" >
<script>
    window.onbeforeunload = function(e) {
        return 'Dialog text here.';
    };

</script>
<h2>ask questions on exit</h2>
</body>
</html>

次に、ハングを再現するサンプルスクリプトを実行しました

from Selenium import webdriver

driver = webdriver.Firefox()

driver.get("http://localhost:8090/index.html")
driver.find_element_by_name("name").send_keys("Tarun")
driver.quit()

これにより、Selenium pythonが無期限にハングします。これは、それ自体は良いことではありません。問題はwindow.onloadおよびwindow.onbeforeunload Seleniumは、発生したときのライフサイクルのために処理が困難です。 onloadは、Seleniumが処理のためにalertconfirmを抑制する独自のコードを挿入する前でも発生します。 onbeforeunloadもSeleniumの手の届かないところにあると確信しています。

したがって、回避する方法は複数あります。

アプリの変更

onloadまたはonbeforeunloadイベントを使用しないように開発者に依頼してください。彼らは耳を傾けますか?わからない

プロファイルでアンロードする前に無効にする

これはあなたがすでにあなたの答えに投稿したものです

from Selenium import webdriver
profile = webdriver.FirefoxProfile()
# other settings here
profile.set_preference("dom.disable_beforeunload", True)
driver = webdriver.Firefox(firefox_profile = profile)

コードを介してイベントを無効にします

try:
    driver.execute_script("window.onunload = null; window.onbeforeunload=null")
finally:
    pass
driver.quit()

これは、複数のタブを開いていない場合、またはポップアップを生成するタブがフォーカスされている場合にのみ機能します。しかし、これはこの状況を処理するための優れた一般的な方法です

セレンをハングさせない

Seleniumがハングする理由は、geckodriverにリクエストを送信し、geckodriverがFirefoxにリクエストを送信し、ユーザーがダイアログを閉じるのを待つ間、これらの1つが応答しないためです。しかし、問題はSelenium pythonドライバーがこの接続部分にタイムアウトを設定しないことです。

この問題を解決するには、2行以下のコードを追加するだけです。

import socket
socket.setdefaulttimeout(10)
try:
   driver.quit()
finally:
   # Set to something higher you want
   socket.setdefaulttimeout(60)

ただし、このアプローチの問題は、ドライバー/ブラウザーがまだ閉じられないことです。これは、以下の回答で説明されているように、ブラウザを強制終了するためのさらに堅牢なアプローチが必要な場所です。

Pythonで、Selenium WebDriverが終了したかどうかを確認する方法は?

回答を完成させるための上記のリンクのコード

from Selenium import webdriver
import psutil

driver = webdriver.Firefox()

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

driver_process = psutil.Process(driver.service.process.pid)

if driver_process.is_running():
    print ("driver is running")

    firefox_process = driver_process.children()
    if firefox_process:
        firefox_process = firefox_process[0]

        if firefox_process.is_running():
            print("Firefox is still running, we can quit")
            driver.quit()
        else:
            print("Firefox is dead, can't quit. Let's kill the driver")
            firefox_process.kill()
    else:
        print("driver has died")
1
Tarun Lalwani

ChromeDriverユーザーの場合:

options = Options()
options.add_argument('no-sandbox')
driver.close()
driver.quit()

クレジット... https://bugs.chromium.org/p/chromedriver/issues/detail?id=1135

0
hyukkyulee

私が理解している限り、私が答えようとする質問は基本的に2つあります。

  1. driver.webdriver.quit()メソッド呼び出しが失敗すると、例外が発生するのではなく、スクリプトがハング/応答しない状態のままになるのはなぜですか?
  2. スクリプトが実行サイクルを完了しなかったのに、なぜテストケースはまだ合格だったのですか?

最初の質問に答えるために、私はSelenium Architectureについて説明しようとします。これにより、ほとんどの疑問が解消されます。

では、Selenium Webdriverはどのように機能しますか?

Selenium Client Libraryを使用して記述したすべてのステートメントまたはコマンドは、http経由で JSON Wire Protocol に変換され、次に渡されます。ブラウザドライバー(chromedriver、geckodriver)に。したがって、基本的に、これらの生成されたhttp URL( [〜#〜] rest [〜#〜] アーキテクチャに基づく)はブラウザドライバに到達します。ブラウザドライバの内部には、受信したURLをReal Browser(HTTP over HTTP Serverとして)に内部的に渡すhttpサーバーがあり、適切な応答が行われます。 Webブラウザによって生成され、ブラウザドライバに(HTTP over HTTPサーバーとして)返送され、次に JSONワイヤプロトコル が使用されます。応答をSelenium Client Libraryに送り返します。これにより、達成された応答に基づいてさらに先に進む方法が最終的に決定されます。詳細については、添付の画像を参照してください。

enter image description here

スクリプトが保留になっている質問に戻ると、受信したリクエストに対してブラウザがまだ機能していると簡単に結論付けることができます。そのため、ブラウザドライバに応答が返されません。 -)次に左Selenium Libraryquit()関数を保留、つまりリクエスト処理の完了を待機します。

したがって、使用できるさまざまな回避策がありますが、そのうちの1つは Alex ですでに説明されています。しかし、私の経験によれば、ブラウザSeleniumが他の場合にもホールド/フリーズ状態のままになる可能性があるため、このような状態を処理するためのはるかに優れた方法があると思います。個人的には タイムアウト付きスレッドキルアプローチ as Seleniumオブジェクトは常にmain() threadで実行されます。メインスレッドに特定の時間を割り当てることができ、タイムアウトセッション時間に達した場合はmain() threadを強制終了できます。

次に、2番目の質問に移ります。

スクリプトが実行サイクルを完了しなかったのに、なぜテストケースはまだ合格だったのですか?

pytestがどのように機能するかについてはあまりわかりませんが、テストエンジンがどのように動作するかについては基本的な考えがあり、それに基づいてこれに答えようとします。

手始めに、完全なスクリプトの実行が完了するまで、テストケースに合格することはまったく不可能です。繰り返しますが、テストケースが合格している場合、次のようなシナリオはほとんどありません。

  • テストメソッドは、実行全体をハング/フリーズ状態のままにするメソッドを使用していません。
  • テスト分解環境内でメソッドを呼び出している必要があります(w.r.t [TestNG][4]テストエンジンJava:@ AfterClass、@ AfterTest、@ AfterGroups、@ AfterMethod、@ AfterSuite)は、テストの実行がすでに完了していることを意味します。したがって、これがテストが正常に完了したと表示される理由である可能性があります。

第二の理由で、どのような適切な原因があるのか​​まだわかりません。何か思いついた場合は、投稿を探して更新します。

@Alex :質問をよりよく理解して更新できますか。つまり、現在のテストデザインを更新して、より適切な説明を見つけることができます。

0
learner8269

Pytestでティアダウンコードを実行することを保証する最良の方法は、ファイナライザー関数を定義し、それをファイナライザーとしてそのフィクスチャに追加することです。これにより、yieldコマンドの前に何かが失敗した場合でも、ティアダウンが発生することが保証されます。

ポップアップがティアダウンをハングアップさせないようにするには、必要なときにいつでもタイムアウトするWebdriverWait.untilコマンドに投資してください。ポップアップが表示され、テストを続行できず、タイムアウトし、ティアダウンが呼び出されます。

0
Aphid