初始化 selenium webdriver 时如何修复 python-selenium 错误“连接被拒绝”?

Posted

技术标签:

【中文标题】初始化 selenium webdriver 时如何修复 python-selenium 错误“连接被拒绝”?【英文标题】:How to fix python-selenium error "connection refused" when initializing a selenium webdriver? 【发布时间】:2019-02-27 17:01:43 【问题描述】:

我正在非公开网页上运行非常复杂的 python-selenium 测试。在大多数情况下,这些测试运行良好,但有时其中一个测试在 webdriver 本身初始化期间失败。

提示:尝试初始化 webdriver 时会发生此错误,即执行以下操作时:

# Start of the tests
mydriver =  webdriver.Firefox(firefox_profile=profile, log_path=logfile)
# ERROR HAPPENS HERE

# Doing other stuff here
....
# Doing tests here
....
# Doing shutdown here
mydriver.quit()

以下是此类错误的完整示例:

___________ ERROR at setup of TestSuite.test_synaptic_events_fitting ___________

>   lambda: ihook(item=item, **kwds),
    when=when,
            )

/usr/local/lib/python2.7/dist-packages/flaky/flaky_pytest_plugin.py:273: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
conftest.py:157: in basedriver
    mydriver = firefox.get_driver(*args)
bsp_usecase_tests/tools/firefox.py:44: in get_driver
    driver = webdriver.Firefox(firefox_profile=profile, log_path=logfile)  #### INITIALIZING OF WEBDRIVER HERE
/usr/local/lib/python2.7/dist-packages/selenium/webdriver/firefox/webdriver.py:158: in __init__
    keep_alive=True)
/usr/local/lib/python2.7/dist-packages/selenium/webdriver/remote/webdriver.py:154: in __init__
    self.start_session(desired_capabilities, browser_profile)
/usr/local/lib/python2.7/dist-packages/selenium/webdriver/remote/webdriver.py:243: in start_session
    response = self.execute(Command.NEW_SESSION, parameters)
/usr/local/lib/python2.7/dist-packages/selenium/webdriver/remote/webdriver.py:311: in execute
    self.error_handler.check_response(response)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <selenium.webdriver.remote.errorhandler.ErrorHandler object at 0x7efd3b702f90>
response = 'status': 500, 'value': '"value":"error":"unknown error","message":"connection refused","stacktrace":"stack backtra...s::imp::thread::impl::new::thread_start\n                        at /checkout/src/libstd/sys/unix/thread.rs:84"'

    def check_response(self, response):
        """
            Checks that a JSON response from the WebDriver does not have an error.

            :Args:
             - response - The JSON response from the WebDriver server as a dictionary
               object.

            :Raises: If the response contains an error message.
            """
        status = response.get('status', None)
        if status is None or status == ErrorCode.SUCCESS:
            return
        value = None
        message = response.get("message", "")
        screen = response.get("screen", "")
        stacktrace = None
        if isinstance(status, int):
            value_json = response.get('value', None)
            if value_json and isinstance(value_json, basestring):
                import json
                try:
                    value = json.loads(value_json)
                    if len(value.keys()) == 1:
                        value = value['value']
                    status = value.get('error', None)
                    if status is None:
                        status = value["status"]
                        message = value["value"]
                        if not isinstance(message, basestring):
                            value = message
                            message = message.get('message')
                    else:
                        message = value.get('message', None)
                except ValueError:
                    pass

        exception_class = ErrorInResponseException
        if status in ErrorCode.NO_SUCH_ELEMENT:
            exception_class = NoSuchElementException
        elif status in ErrorCode.NO_SUCH_FRAME:
            exception_class = NoSuchFrameException
        elif status in ErrorCode.NO_SUCH_WINDOW:
            exception_class = NoSuchWindowException
        elif status in ErrorCode.STALE_ELEMENT_REFERENCE:
            exception_class = StaleElementReferenceException
        elif status in ErrorCode.ELEMENT_NOT_VISIBLE:
            exception_class = ElementNotVisibleException
        elif status in ErrorCode.INVALID_ELEMENT_STATE:
            exception_class = InvalidElementStateException
        elif status in ErrorCode.INVALID_SELECTOR \
                or status in ErrorCode.INVALID_XPATH_SELECTOR \
                or status in ErrorCode.INVALID_XPATH_SELECTOR_RETURN_TYPER:
            exception_class = InvalidSelectorException
        elif status in ErrorCode.ELEMENT_IS_NOT_SELECTABLE:
            exception_class = ElementNotSelectableException
        elif status in ErrorCode.ELEMENT_NOT_INTERACTABLE:
            exception_class = ElementNotInteractableException
        elif status in ErrorCode.INVALID_COOKIE_DOMAIN:
            exception_class = InvalidCookieDomainException
        elif status in ErrorCode.UNABLE_TO_SET_COOKIE:
            exception_class = UnableToSetCookieException
        elif status in ErrorCode.TIMEOUT:
            exception_class = TimeoutException
        elif status in ErrorCode.SCRIPT_TIMEOUT:
            exception_class = TimeoutException
        elif status in ErrorCode.UNKNOWN_ERROR:
            exception_class = WebDriverException
        elif status in ErrorCode.UNEXPECTED_ALERT_OPEN:
            exception_class = UnexpectedAlertPresentException
        elif status in ErrorCode.NO_ALERT_OPEN:
            exception_class = NoAlertPresentException
        elif status in ErrorCode.IME_NOT_AVAILABLE:
            exception_class = ImeNotAvailableException
        elif status in ErrorCode.IME_ENGINE_ACTIVATION_FAILED:
            exception_class = ImeActivationFailedException
        elif status in ErrorCode.MOVE_TARGET_OUT_OF_BOUNDS:
            exception_class = MoveTargetOutOfBoundsException
        elif status in ErrorCode.javascript_ERROR:
            exception_class = JavascriptException
        elif status in ErrorCode.SESSION_NOT_CREATED:
            exception_class = SessionNotCreatedException
        elif status in ErrorCode.INVALID_ARGUMENT:
            exception_class = InvalidArgumentException
        elif status in ErrorCode.NO_SUCH_COOKIE:
            exception_class = NoSuchCookieException
        elif status in ErrorCode.UNABLE_TO_CAPTURE_SCREEN:
            exception_class = ScreenshotException
        elif status in ErrorCode.ELEMENT_CLICK_INTERCEPTED:
            exception_class = ElementClickInterceptedException
        elif status in ErrorCode.INSECURE_CERTIFICATE:
            exception_class = InsecureCertificateException
        elif status in ErrorCode.INVALID_COORDINATES:
            exception_class = InvalidCoordinatesException
        elif status in ErrorCode.INVALID_SESSION_ID:
            exception_class = InvalidSessionIdException
        elif status in ErrorCode.UNKNOWN_METHOD:
            exception_class = UnknownMethodException
        else:
            exception_class = WebDriverException
        if value == '' or value is None:
            value = response['value']
        if isinstance(value, basestring):
            if exception_class == ErrorInResponseException:
                raise exception_class(response, value)
            raise exception_class(value)
        if message == "" and 'message' in value:
            message = value['message']

        screen = None
        if 'screen' in value:
            screen = value['screen']

        stacktrace = None
        if 'stackTrace' in value and value['stackTrace']:
            stacktrace = []
            try:
                for frame in value['stackTrace']:
                    line = self._value_or_default(frame, 'lineNumber', '')
                    file = self._value_or_default(frame, 'fileName', '<anonymous>')
                    if line:
                        file = "%s:%s" % (file, line)
                    meth = self._value_or_default(frame, 'methodName', '<anonymous>')
                    if 'className' in frame:
                        meth = "%s.%s" % (frame['className'], meth)
                    msg = "    at %s (%s)"
                    msg = msg % (meth, file)
                    stacktrace.append(msg)
            except TypeError:
                pass
        if exception_class == ErrorInResponseException:
            raise exception_class(response, message)
        elif exception_class == UnexpectedAlertPresentException and 'alert' in value:
            raise exception_class(message, screen, stacktrace, value['alert'].get('text'))
>       raise exception_class(message, screen, stacktrace)
E       WebDriverException: Message: connection refused

/usr/local/lib/python2.7/dist-packages/selenium/webdriver/remote/errorhandler.py:237: WebDriverException

这些测试作为 jenkins 计划的一部分在 docker 容器内运行,以确保始终保持完全相同的环境。以下是使用的软件包及其版本的列表:

python 2.7.12 pytest 3.6.1 硒 3.8.0 geckodriver 0.19.1 火狐62.0 不稳定的 3.4.0

该错误大约出现在所有测试的 1% 中。大约有 15 种不同的测试,错误似乎是随机出现的(即并不总是相同的测试)。

这是 firefox/selenium/geckodriver 中的错误吗?有没有办法解决这个问题?

下面的代码sn -p 不是我用的一些代码!这只是如何解决上述问题的一个想法。这可能是解决我最初问题的好方法吗?

while counter<5:
    try:
        webdriver = webdriver.Firefox(firefox_profile=profile, log_path=logfile) 
        break
    except WebDriverException:
        counter +=1

有没有更好的方法来做到这一点?

【问题讨论】:

尝试更新 Selenium (3.14.0) 和 geckodriver (0.22.0)。那里有很多变化,包括初始连接。 不涉及任何异步或多重处理对吧?完成一项测试后,您有 close()quit() 网络驱动程序吗? 是的;我不涉及任何多处理,我只使用“退出”... @Alex 你能用完整的错误堆栈跟踪更新问题吗? 使用quit 只适合firefox(只要找到它)。顺便说一句,我无法通过简单地向 google.com 发送大量请求来重现问题,您确定问题不是来自您的测试页面吗? 【参考方案1】:

此错误消息...

'status': 500, 'value': '"value":"error":"unknown error","message":"connection refused","stacktrace":"stack backtra...s::imp::thread::impl::new::thread_start\n at /checkout/src/libstd/sys/unix/thread.rs:84"'

...暗示 GeckoDriver 无法启动/产生新的 WebBrowsing 会话,即 Firefox 浏览器 会话。

在comment 的讨论中DELETE '/session/session id' no longer working @andreastt 提到:

geckodriver 在最后一个窗口关闭时隐式结束(前一个)会话。如果driver.quit() 作为后续命令调用,它将失败,因为会话已被隐式删除。

在这些情况下,GeckoDriver 应该检测到会话在driver.close() 之后隐式关闭,或者在会话已经关闭的情况下忽略来自driver.quit() 的响应。

在这种情况下,会生成以下跟踪日志:

1505753594121   webdriver::server   DEBUG   Last window was closed, deleting session
1505753594121   webdriver::server   DEBUG   Deleting session
1505753594121   geckodriver::marionette DEBUG   Stopping browser process
1505753594364   webdriver::server   DEBUG   <- 200 OK "value": []
1505753594523   webdriver::server   DEBUG   -> DELETE /session/a8312282-af00-4931-94d4-0d401abf01c9 
1505753594524   webdriver::server   DEBUG   <- 500 Internal Server Error "value":"error":"session not created","message":"Tried to run command without establishing a connection","stacktrace":"stack backtrace:\n   0:           0x4f388c - backtrace::backtrace::trace::h736111741fa0878e\n   1:           0x4f38c2 - backtrace::capture::Backtrace::new::h63b8a5c0787510c9\n   2:           0x442c61 - webdriver::error::WebDriverError::new::hc4fe6a1ced4e57dd\n   3:           0x42a926 - <webdriver::server::Dispatcher<T, U>>::run::hba9181b5aacf8f04\n   4:           0x402c59 - std::sys_common::backtrace::__rust_begin_short_backtrace::h19de262639927233\n   5:           0x40c065 - std::panicking::try::do_call::h6c1659fc4d01af51\n   6:           0x5e38ec - panic_unwind::__rust_maybe_catch_panic\n                        at /checkout/src/libpanic_unwind/lib.rs:98\n   7:           0x420d32 - <F as alloc::boxed::FnBox<A>>::call_box::h953e5f59694972c5\n   8:           0x5dc00b - alloc::boxed::impl::call_once<(),()>\n                        at /checkout/src/liballoc/boxed.rs:661\n                         - std::sys_common::thread::start_thread\n                        at /checkout/src/libstd/sys_common/thread.rs:21\n                         - std::sys::imp::thread::impl::new::thread_start\n                        at /checkout/src/libstd/sys/unix/thread.rs:84"
1505753594533   webdriver::server   DEBUG   -> DELETE /session/a8312282-af00-4931-94d4-0d401abf01c9 
1505753594542   webdriver::server   DEBUG   <- 500 Internal Server Error "value":"error":"session not created","message":"Tried to run command without establishing a connection","stacktrace":"stack backtrace:\n   0:           0x4f388c - backtrace::backtrace::trace::h736111741fa0878e\n   1:           0x4f38c2 - backtrace::capture::Backtrace::new::h63b8a5c0787510c9\n   2:           0x442c61 - webdriver::error::WebDriverError::new::hc4fe6a1ced4e57dd\n   3:           0x42a926 - <webdriver::server::Dispatcher<T, U>>::run::hba9181b5aacf8f04\n   4:           0x402c59 - std::sys_common::backtrace::__rust_begin_short_backtrace::h19de262639927233\n   5:           0x40c065 - std::panicking::try::do_call::h6c1659fc4d01af51\n   6:           0x5e38ec - panic_unwind::__rust_maybe_catch_panic\n                        at /checkout/src/libpanic_unwind/lib.rs:98\n   7:           0x420d32 - <F as alloc::boxed::FnBox<A>>::call_box::h953e5f59694972c5\n   8:           0x5dc00b - alloc::boxed::impl::call_once<(),()>\n                        at /checkout/src/liballoc/boxed.rs:661\n                         - std::sys_common::thread::start_thread\n                        at /checkout/src/libstd/sys_common/thread.rs:21\n                         - std::sys::imp::thread::impl::new::thread_start\n                        at /checkout/src/libstd/sys/unix/thread.rs:84"
1505753594549   webdriver::server   DEBUG   -> GET /shutdown 
1505753594551   webdriver::server DEBUG <- 404 Not Found "value":"error":"unknown command","message":"GET /shutdown did not match a known command","stacktrace":"stack backtrace:\n 0: 0x4f388c - backtrace::backtrace::trace::h736111741fa0878e\n 1: 0x4f38c2 - backtrace::capture::Backtrace::new::h63b8a5c0787510c9\n 2: 0x442d88 - webdriver::error::WebDriverError::new::hea6d4dbf778b2b24\n 3: 0x43c65f - <webdriver::server::HttpHandler<U> as hyper::server::Handler>::handle::hd03629bd67672697\n 4: 0x403a04 - std::sys_common::backtrace::__rust_begin_short_backtrace::h32e6ff325c0d7f46\n 5: 0x40c036 - std::panicking::try::do_call::h5f902dc1eea01ffe\n 6: 0x5e38ec - panic_unwind::__rust_maybe_catch_panic\n at /checkout/src/libpanic_unwind/lib.rs:98\n 7: 0x4209a2 - <F as alloc::boxed::FnBox<A>>::call_box::h032bafb4b576d1cd\n 8: 0x5dc00b - alloc::boxed::impl::call_once<(),()>\n 

虽然您看到的错误的错误代码是 'status': 500 并且我提供的错误示例是 404 Not Found,但显然看起来不同,核心原因类似于:

"message":"connection refused" 

由于:

imp::thread::impl::new::thread_start

来自:

/checkout/src/libstd/sys/unix/thread.rs:84

从另一个角度来看,当您使用 GeckoDriverSeleniumFirefox 时,请确保二进制文件的兼容性如下:


分析

geckodriver 0.19.1 推出以来,geckodriver 二进制文件发生了重大变化。一些变化是:

GeckoDriver v0.22.0(2018-09-15): 用于 [脚本超时] 和 [超时] 错误的 HTTP 状态代码已从请求超时 (408) 更改为内部服务器错误 (500),以不破坏 HTTP/1.1 Keep-Alive 支持,正如 HTTP 客户端所解释的那样旧状态代码表示他们应该重复请求。 持续连接的 HTTP/1.1 Keep-Alive 超时已增加到 90 秒。 现在没有活动会话时会返回 [invalid session ID] 错误。 geckodriver 连接到 Marionette 时的握手已通过在失败时终止 Firefox 进程而得到加强。 握手读取超时已减少到 10 秒,而不是永远等待。 GeckoDriver v0.21.0(2018-06-15): 没有返回值的 WebDriver 命令现在可以正确返回 value: null 而不是空字典。 强制使用 IPv4 网络堆栈。

在某些系统配置中,localhost 解析为 IPv6 地址,geckodriver 会尝试连接到错误 IP 堆栈上的 Firefox,导致连接尝试在 60 秒后超时。我们现在确保 geckodriver 始终使用 IPv4 来连接到 Firefox 并分配空闲端口。

GeckoDriver v0.20.1(2018-04-06): 避免试图杀死已停止的 Firefox 进程。

随着在 0.20.0 中允许 Firefox 有足够的时间关闭的更改,geckodriver 开始无条件地终止进程以获取其退出状态。这导致 geckodriver 将成功关闭 Firefox 错误地报告为失败。

GeckoDriver v0.20.0(2018-03-08): geckodriver 的回溯不再替代缺失的 Marionette 堆栈跟踪。 Firefox 进程现在有足够的时间关闭,从而有足够的时间让 Firefox 关闭挂起监视器启动。

Firefox 有一个集成的后台监视器,可以在关机期间观察长时间运行的线程。如果发生挂起,这些线程将在 63 秒后被终止。要让 Firefox 自行关闭这些线程,geckodriver 必须等待这段时间和额外的几秒钟。


解决方案

Selenium升级到当前级别Version 3.14.0。 将GeckoDriver升级到GeckoDriver v0.22.0级别。 将 Firefox 版本升级到 Firefox v62.0.2 级别。 如果您的基础 Web Client 版本太旧,请通过 Revo Uninstaller 卸载它并安装最新的 GA 和发布版本的 Web Client。 始终在 tearDown() 方法中调用 driver.quit() 以优雅地关闭和销毁 WebDriverWeb Client 实例。 以非 root 用户身份执行 Test

更新

根据您的问题更新粗略,您可以引发一个循环进行多次试验以初始化 selenium webdriver 实例,如下所示:

通过调用taskkill 命令(WindowsOS 特定)确保没有 geckodriver 的悬空实例,如下所示:

os.system("taskkill /f /im geckodriver.exe /T") 

通过调用kill() 命令(跨平台)确保没有 geckodriver 的悬空实例,如下所示:

from selenium import webdriver
import psutil
from selenium.common.exceptions import WebDriverException

for counter in range(5):
    try:
        webdriver = webdriver.Firefox(executable_path=r'C:\Utility\BrowserDrivers\geckodriver.exe') 
        print("WebDriver and WebBrowser initialized ...")
        break
    except WebDriverException:
        #Cross platform
        PROCNAME = "geckodriver"
        for proc in psutil.process_iter():
            # check whether the process name matches
            if proc.name() == PROCNAME:
                proc.kill()        
        print("Retrying ...")
print("Out of loop ...")

【讨论】:

@Alex 从您的代码试验中可以清楚地看出,您正在使用while counter&lt;5 中的循环来启动 webdriver 进程,该进程保证了 clean quit() 没有。该循环代码 sn-p 只是可能解决方案的建议。我在问那个循环代码 sn-p 是否是解决我原来的问题的好方法...... @Alex 我没有符合您要求的 测试环境 selenium 3.8.0, geckodriver 0.19.1 和 firefox 62.0。因此,我们的努力是根据您提供的最少日志和代码试验来构建答案。 是的。我确实已经更新了 selenium 版本和 geckodriver 编号,但很难说它现在可以工作(或更好),因为这个错误只出现在所有情况下的 1% 甚至更少...... 在打开新连接之前运行 quit() 解决了我使用 RSelenium 的问题

以上是关于初始化 selenium webdriver 时如何修复 python-selenium 错误“连接被拒绝”?的主要内容,如果未能解决你的问题,请参考以下文章

Selenium WebDriver的使用

Selenium.WebDriver - 当我尝试在 Chrome 中运行测试时出现错误

Selenium Webdriver 和 PageFactory 初始化 List<WebElement> 元素

零基础学习Selenium自动化测试--WebDriver对浏览器的操作

如何使用无头(gui-less)Selenium WebDriver下载文件

selenium + python实现截图并且保存图片