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:
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?
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 dedriver.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:
Il y avait eu des changements importants dans le geckodriver binaire depuis la disponibilité de geckodriver 0.19.1 . Quelques changements sont les suivants:
Keep-Alive
, Comme interprètent les clients HTTP l'ancien code d'état signifie qu'ils doivent dupliquer la demande.Keep-Alive
Pour les connexions persistantes a été augmenté à 90 secondes.{value: null}
Au lieu d'un dictionnaire vide.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.
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.
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.
driver.quit()
dans la méthode tearDown(){}
pour fermer et détruire les instances WebDriver et Web Client avec élégance.Test
en tant qu'utilisateur non root.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 ...")