web-dev-qa-db-fra.com

Comment réparer l'erreur "connexion refusée" lors de l'initialisation d'un pilote Web Selenium?

J'exécute des tests python-sélénium très complexes sur des pages Web non publiques. Dans la plupart des cas, ces tests fonctionnent correctement, mais parfois l'un de ces tests échoue lors de l'initialisation du pilote Web lui-même.

Astuce: cette erreur se produit lorsque vous essayez d'initialiser un pilote Web, c'est-à-dire lorsque vous faites quelque chose comme ceci:

# 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()

Voici un exemple complet d'une telle erreur:

___________ 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

Ces tests sont exécutés dans le cadre d'un plan jenkins à l'intérieur d'un conteneur Docker, pour garantir le même environnement exact tout le temps. Voici une liste des packages utilisés et leurs versions:

  • python 2.7.12
  • pytest 3.6.1
  • Sélénium 3.8.0
  • geckodriver 0.19.1
  • firefox 62.0
  • floconneux 3.4.0

L'erreur apparaît approximativement dans environ 1% de tous les tests. Il existe environ 15 tests différents, et l'erreur semble apparaître de manière aléatoire (c'est-à-dire pas toujours le même test).

Est-ce un bug dans firefox/Selenium/geckodriver? Et existe-t-il un moyen de résoudre ce problème?

L'extrait de code suivant ce n'est pas un code que j'utilise! C'est juste une idée de la façon de résoudre mon problème décrit ci-dessus. Est-ce peut-être un bon moyen de résoudre mon problème d'origine ou non?

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

Y a-t-il une meilleure manière de faire cela?

13
Alex

Ce message d'erreur ...

{'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"}}'}

... implique que le GeckoDriver n'a pas pu lancer/générer une nouvelle session de navigation Web ie Session du navigateur Firefox .

Dans un commentaire dans la discussion SUPPRIMER '/ session/{id session}' ne fonctionne plus @andreastt mentionne que:

geckodriver met fin implicitement à la session (précédente) lorsque la dernière fenêtre se ferme. Si driver.quit() est appelée comme commande suivante, elle échouera car la session a déjà été implicitement supprimée.

Dans ces cas, GeckoDriver doit détecter que la session a été fermée implicitement après driver.close() ou ignorer la réponse de driver.quit() au cas où la session a déjà été fermée.

Dans de tels cas, les journaux de suivi suivants sont générés:

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 

Bien que les codes d'erreur pour l'erreur que vous voyez soient 'status': 500 et l'exemple d'erreur que j'ai fourni est 404 Introuvable , semble différent, la raison principale est la suivante:

"message":"connection refused" 

en raison de:

imp::thread::{{impl}}::new::thread_start

de:

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

D'un autre point de vue, pendant que vous utilisez GeckoDriver , Sélénium et Firefox s'assure que les binaires sont compatibles comme suit:

Geckodriver Releases


Une analyse

Il y avait eu des changements importants dans le geckodriver binaire depuis la disponibilité de geckodriver 0.19.1 . Quelques changements sont les suivants:

  • GeckoDriver v0.22.0 (2018-09-15):
    • Les codes d'état HTTP utilisés pour les erreurs [délai d'expiration du script] et [délai d'expiration] sont passés de Délai d'expiration de requête (408) à Erreur de serveur interne (500) afin de ne pas interrompre la prise en charge HTTP/1.1 Keep-Alive, Comme interprètent les clients HTTP l'ancien code d'état signifie qu'ils doivent dupliquer la demande.
    • Le délai d'attente HTTP/1.1 Keep-Alive Pour les connexions persistantes a été augmenté à 90 secondes.
    • Une erreur [ID de session non valide] est maintenant renvoyée lorsqu'il n'y a pas de session active.
    • La poignée de main lorsque geckodriver se connecte à Marionette a été renforcée en tuant le processus Firefox en cas d'échec.
    • Le délai de lecture de la prise de contact a été réduit à 10 secondes au lieu d'attendre indéfiniment.
  • GeckoDriver v0.21.0 (2018-06-15):
    • Les commandes WebDriver qui n'ont pas de valeur de retour renvoient désormais correctement {value: null} Au lieu d'un dictionnaire vide.
    • Forcer l'utilisation de la pile réseau IPv4.
    • Sur certaines configurations système, où localhost se résout en une adresse IPv6, geckodriver tenterait de se connecter à Firefox sur la mauvaise pile IP, ce qui entraînerait l'expiration de la tentative de connexion après 60 secondes. Nous nous assurons maintenant que geckodriver utilise IPv4 de manière cohérente à la fois pour se connecter à Firefox et pour allouer un port libre.

  • GeckoDriver v0.20.1 (2018-04-06):
    • Évitez de tenter de tuer le processus Firefox qui s'est arrêté.
    • Avec le changement pour laisser suffisamment de temps à Firefox pour s'arrêter en 0.20.0, geckodriver a commencé à tuer inconditionnellement le processus pour récolter son statut de sortie. Cela a conduit geckodriver à signaler de manière inexacte un arrêt réussi de Firefox comme un échec.

  • GeckoDriver v0.20.0 (2018-03-08):
    • Les backtraces de geckodriver ne remplacent plus les traces de stack de Marionette manquantes.
    • Le processus Firefox dispose désormais de suffisamment de temps pour s'arrêter, ce qui laisse suffisamment de temps au moniteur d'arrêt de Firefox pour qu'il se déclenche.
    • Firefox a un moniteur d'arrière-plan intégré qui observe les threads de longue durée lors de l'arrêt. Ces threads seront tués après 63 secondes en cas de blocage. Pour permettre à Firefox de fermer ces threads par lui-même, geckodriver doit attendre ce temps et quelques secondes supplémentaires.


Solution

  • Mettre à niveau Selenium aux niveaux actuels Version 3.14.0 .
  • Mettre à niveau GeckoDriver vers GeckoDriver v0.22.0 niveau.
  • Mettre à niveau la version Firefox vers les niveaux Firefox v62.0.2.
  • Si votre version de base Web Client est trop ancienne, désinstallez-la via Revo Uninstaller et installez une version récente GA et version publiée de Web Client.
  • Appelez toujours driver.quit() dans la méthode tearDown(){} pour fermer et détruire les instances WebDriver et Web Client avec élégance.
  • Exécutez votre Test en tant qu'utilisateur non root.

Mettre à jour

Selon la mise à jour de votre question, vous pouvez induire une boucle pour plusieurs essais pour initialiser l'instance de Webdriver Selenium comme suit:

  • Assurez-vous qu'il n'y a pas d'instances pendantes de geckodriver en appelant la commande taskkill ( spécifique à WindowsOS ) comme suit:

    os.system("taskkill /f /im geckodriver.exe /T") 
    
  • Assurez-vous qu'il n'y a pas d'instances pendantes de geckodriver en appelant la commande kill() ( Multiplateforme ) comme suit:

    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 ...")
    
6
DebanjanB