为什么python-selenium-webdriver'退出'不会退出?

时间:2018-02-09 10:26:13

标签: python selenium pytest

我有一个非常复杂的py.test python-selenium测试设置,我在py.test灯具内创建了一个Firefox webdriver。以下是我正在做的事情的一些想法:

'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打印出来。整个过程此时停止,似乎没有继续进行。整个硒测试都停留在webdriver.quit)

然而,测试成功完成......

这种行为有什么理由?

附录:

执行挂起的原因似乎是一个弹出窗口,询问用户是否因为未保存的数据而要离开页面。这意味着quit方法的文档不正确。它声明:

Quits the driver and close every associated window.

6 个答案:

答案 0 :(得分:6)

这是一个非平凡的问题,硒的作用实际上是不一致的。如文档所述,quit方法应该只关闭浏览器窗口,但事实并非如此。相反,您会弹出一个询问用户是否要离开页面的弹出窗口:

enter image description here

令人讨厌的是,此弹出窗口仅在用户调用

后出现
driver.quit()

解决此问题的一种方法是为驱动程序设置以下配置文件

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

答案 1 :(得分:3)

默认情况下,关闭警告在Firefox中是正确的,您可以在about:config中看到,并且可以为您的个人资料禁用它们:

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

查看python/site-packages/selenium/webdriver/firefox/webdriver_prefs.json json

Default Preferences

答案 2 :(得分:1)

所以我能够使用下面的示例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.onloadwindow.onbeforeunload很难让Selenium处理,因为它发生的生命周期。 onload甚至在selenium注入了自己的代码来抑制alertconfirm进行处理之前就已经发生了onbeforeunload。我很确定onload也无法达到硒。

因此,有多种方法可以解决。

应用更改

要求开发者不要使用onbeforeunloadfrom selenium import webdriver profile = webdriver.FirefoxProfile() # other settings here profile.set_preference("dom.disable_beforeunload", True) driver = webdriver.Firefox(firefox_profile = profile) 个事件。他们会听吗?不确定

禁用配置文件中的beforeunload

这是您在答案中发布的内容

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

通过代码

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

只有当您没有打开多个标签或者生成弹出窗口的标签处于焦点时,这才有效。但是处理这种情况是一种很好的通用方法

不让Selenium挂起

selenium挂起的原因是它向geckodriver发送请求然后将其发送到firefox,其中一个只是在等待用户关闭对话框时没有响应。但问题是Selenium python驱动程序没有在此连接部分设置任何超时。

要解决这个问题,就像添加以下两行代码一样简单

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")

但这种方法的问题是驱动程序/浏览器仍然无法关闭。这就是你需要更强大的方法来杀死浏览器,如下面的回答

所述

In Python, how to check if Selenium WebDriver has quit or not?

上述链接中的代码,用于完成答案

updateSelectizeInput

答案 3 :(得分:0)

据我所知,基本上有两个问题我会尽力回答:

  
      
  1. 为什么driver.webdriver.quit()方法调用失败会使脚本处于挂起/无响应状态而不是引发任何异常?
  2.   
  3. 如果脚本从未完成它的执行周期,为什么测试用例仍然是一个传递?
  4.   

为了回答第一个问题,我将尝试解释 Selenium Architecture ,这将清除我们的大部分疑虑。

  

那么Selenium Webdriver的功能如何?

您使用 Selenium Client Library 编写的每个语句或命令都将通过http转换为JSON Wire Protocol,而这又将传递给我们的浏览器驱动程序(chromedriver,geckodriver)。所以基本上这些生成的http URL(基于REST架构)到达浏览器驱动程序。在浏览器驱动程序内部有一些http服务器,它们会在内部将收到的URL传递给 Real Browser (作为HTTP over HTTP Server),Web浏览器将生成相应的响应并发送回浏览器驱动程序(作为HTTP over HTTP服务器)反过来将使用JSON Wire Protocol将响应发送回 Selenium客户端库,最终将决定如何继续进行做出了回应。有关更多说明,请参阅附图:

enter image description here

现在回到脚本处于暂停状态的问题,我们可以简单地得出结论,我们的浏览器仍在处理请求,这就是为什么没有响应被发送回浏览器驱动程序左转 Selenium Library quit()函数保持等待请求处理完成。

因此,我们可以使用各种变通方法,其中一个已由Alex解释。但我相信有更好的方法来处理这样的情况,因为根据我的经验,浏览器Selenium可能会让我们处于暂停/冻结状态,因此我个人更喜欢Thread Kill Approach with Timeout,因为Selenium对象始终在{{}运行1}}。我们可以为主线程分配一个特定的时间,如果达到超时会话时间,可以杀死main() thread

现在转到第二个问题:

  

如果脚本从未完成,那么为什么测试用例仍然是一个通行证   执行周期?

好吧,我对main() thread的工作方式并不了解,但我对测试引擎的运作方式有了基本的了解,我会尝试回答这个问题。

对于初学者来说,在完成完整的脚本运行之前,任何测试用例都无法通过。同样,如果您的测试用例正在通过,那么可能的情况很少,例如:

  • 您的测试方法从未使用过使整个执行处于挂起/冻结状态的方法。
  • 您必须在测试拆除环境中调用该方法(在Java中使用w:r.t pytest测试引擎:@AfterClass,@ AfterTest,@ AfterGroups,@ AfterMethod,@ AfterSuite)意味着您的测试执行已经完成。因此,这可能是测试显示成功完成的原因。

我仍然不确定第二个原因是什么原因。如果想出来的话,我会继续查看并更新帖子。

@Alex:您能否以更好的理解更新问题,即您可以探索的当前测试设计,以找到更好的解释。

答案 4 :(得分:0)

保证在pytest中运行拆卸代码的最佳方法是定义终结器函数并将其作为终结器添加到该灯具中。这可以保证即使在yield命令之前发生了某些事情,你仍然会得到你的拆解。

为避免弹出窗口挂断您的拆卸,请投资一些WebdriverWait.until命令,这些命令会在您需要时暂停。出现弹出窗口,测试无法继续,超时,拆除被调用。

答案 5 :(得分:0)

对于ChromeDriver用户:

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

抵免额... https://bugs.chromium.org/p/chromedriver/issues/detail?id=1135