¿Cómo corregir el error de python-selenium “conexión rechazada” al inicializar un controlador de web de selenium?

Estoy ejecutando pruebas muy complejas de python-selenium en páginas web no públicas. En la mayoría de los casos, estas pruebas funcionan bien, pero a veces una de estas pruebas falla durante la inicialización del controlador web.

Sugerencia: este error se produce al intentar inicializar un webdriver, es decir, cuando se hace algo como esto:

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

Aquí hay un ejemplo completo de tal error:

 ___________ 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 =  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', '') if line: file = "%s:%s" % (file, line) meth = self._value_or_default(frame, 'methodName', '') 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 

Estas pruebas se ejecutan como parte de un plan de Jenkins dentro de un contenedor de la ventana acoplable, para garantizar el mismo entorno en todo momento. Aquí hay una lista de los paquetes usados ​​y sus versiones:

  • python 2.7.12
  • pytest 3.6.1
  • selenium 3.8.0
  • geckodriver 0.19.1
  • Firefox 62.0
  • 3.4.0 escamosa

El error aparece aproximadamente en aproximadamente el 1% de todas las pruebas. Hay alrededor de 15 pruebas diferentes, y el error parece aparecer al azar (es decir, no siempre es la misma prueba).

¿Es esto un error en firefox / selenium / geckodriver? ¿Y hay una manera de arreglar esto?

¡El siguiente fragmento de código no es un código que estoy usando ! Es solo una idea de cómo solucionar mi problema descrito anteriormente. ¿Es esta tal vez una buena manera de resolver mi problema original, o no?

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

¿Hay una mejor manera de hacer esto?

Este mensaje de error …

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

… implica que el GeckoDriver no pudo iniciar / generar una nueva sesión de WebBrowsing, es decir, una sesión del navegador Firefox .

En un comentario dentro de la discusión, DELETE ‘/ session / {session id}’ ya no funciona, @andreastt menciona que:

geckodriver está terminando implícitamente la sesión (anterior) cuando se cierra la última ventana. Si se llama a driver.quit() como comando subsiguiente, fallará porque la sesión ya se ha eliminado implícitamente.

En estos casos, GeckoDriver debería detectar que la sesión se ha cerrado implícitamente después de driver.close() o ignorar la respuesta de driver.quit() en caso de que la sesión ya haya sido cerrada.

En tales casos se generan los siguientes registros de seguimiento:

 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 - >::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 - >::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 - >::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 - >::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 -  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 - >::call_box::h032bafb4b576d1cd\n 8: 0x5dc00b - alloc::boxed::{{impl}}::call_once<(),()>\n 

Aunque los códigos de error para el error que está viendo son “estado”: 500 y la muestra de error que proporcioné es 404 No encontrado , aparentemente se ve diferente, la razón principal es similar a:

 "message":"connection refused" 

debido a:

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

desde:

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

Desde otra perspectiva, mientras usa GeckoDriver , Selenium y Firefox, asegúrese de que los binarios sean compatibles de la siguiente manera:

Lanzamientos de Geckodriver


Análisis

Ha habido cambios significativos en el binario geckodriver desde la disponibilidad de geckodriver 0.19.1 . Algunos de los cambios son:

  • GeckoDriver v0.22.0 (2018-09-15):
    • Los códigos de estado HTTP utilizados para los errores de [tiempo de espera de secuencia de comandos] y [tiempo de espera] han cambiado de Tiempo de espera de solicitud (408) a Error interno del servidor (500) para no interrumpir el soporte de mantenimiento de HTTP / 1.1 Keep-Alive , ya que los clientes HTTP interpretan el estado anterior Código que significa que deben duplicar la solicitud.
    • El tiempo de espera Keep-Alive HTTP / 1.1 para conexiones persistentes se ha aumentado a 90 segundos.
    • Ahora se devuelve un error [ID de sesión no válida] cuando no hay una sesión activa.
    • El apretón de manos cuando geckodriver se conecta a Marionette se ha endurecido al eliminar el proceso de Firefox si falla.
    • El tiempo de espera de lectura del handshake se ha reducido a 10 segundos en lugar de esperar para siempre.
  • GeckoDriver v0.21.0 (2018-06-15):
    • Los comandos de WebDriver que no tienen un valor de retorno ahora devuelven correctamente {value: null} lugar de un diccionario vacío.
    • Forzar el uso de la stack de red IPv4.
    • En ciertas configuraciones del sistema, donde localhost resuelve en una dirección IPv6, geckodriver intentará conectarse a Firefox en la stack de IP incorrecta, lo que provocará que el bash de conexión se desactive después de 60 segundos. Ahora nos aseguramos de que geckodriver use IPv4 de forma consistente para conectarse a Firefox y para asignar un puerto libre.

  • GeckoDriver v0.20.1 (2018-04-06):
    • Evita intentar matar el proceso de Firefox que se ha detenido.
    • Con el cambio para permitir que Firefox se apague el tiempo suficiente en 0.20.0, geckodriver comenzó incondicionalmente a matar el proceso para obtener su estado de salida. Esto causó que geckodriver informara incorrectamente de un cierre exitoso de Firefox como una falla.

  • GeckoDriver v0.20.0 (2018-03-08):
    • Los backtraces de geckodriver ya no sustituyen a los stacktraces perdidos de Marionette.
    • El proceso de Firefox ahora tiene suficiente tiempo para apagarse, lo que permite suficiente tiempo para que se active el monitor de locking de Firefox.
    • Firefox tiene un monitor de fondo integrado que observa los hilos de ejecución prolongada durante el apagado. Estos hilos se eliminarán después de 63 segundos en el caso de un locking. Para permitir que Firefox cierre estos subprocesos por sí solo, geckodriver tiene que esperar ese tiempo y algunos segundos adicionales.


Solución

  • Actualizar Selenium a los niveles actuales Versión 3.14.0 .
  • Actualice GeckoDriver al nivel GeckoDriver v0.22.0 .
  • Actualiza la versión de Firefox a los niveles de Firefox v62.0.2 .
  • Si la versión básica de su cliente web es demasiado antigua, desinstálela a través de Revo Uninstaller e instale un GA reciente y una versión de lanzamiento del cliente web .
  • Siempre invoque driver.quit() dentro del tearDown(){} para cerrar y destruir las instancias de WebDriver y Web Client con gracia.
  • Ejecute su Test como un usuario no root.

Actualizar

De acuerdo con la actualización de su pregunta, puede generar un bucle para varias pruebas para inicializar la instancia de Selenium Webdriver de la siguiente manera:

  • Asegúrese de que no haya instancias colgantes de geckodriver invocando el comando taskkill ( específico de WindowsOS ) de la siguiente manera:

     os.system("taskkill /f /im geckodriver.exe /T") 
  • Asegúrese de que no haya instancias colgantes de geckodriver invocando el comando kill() ( plataforma cruzada ) de la siguiente manera:

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