Cómo lidiar con los errores esporádicos BadStatusLine, CannotSendRequest errores en Python WebDriver

Desde que comenzamos a ejecutar las pruebas de la interfaz de usuario de selenium en Jenkins, notamos una pequeña pero molesta frecuencia de errores durante las pruebas. Recibimos errores de BadStatusLine y CannotSendRequest en acciones de selenium aparentemente aleatorias (hacer clic, salir, visitar, etc.).

Por lo general, se verían algo como:

File "/usr/lib/python2.7/unittest/case.py", line 327, in run testMethod() File "/home/jenkins/workspace/Create and Upload Functional Testing/shapeways/test_suite/Portal/CreateAndUpload/TestUploadWhenNotLoggedIn_ExpectLoginModal.py", line 22, in runTest self.dw.visit(ShapewaysUrlBuilder.build_model_upload_url()) File "/home/jenkins/workspace/Create and Upload Functional Testing/webdriver/webdriverwrapper/WebDriverWrapper.py", line 212, in visit return self.execute_and_handle_webdriver_exceptions(lambda: _visit(url)) File "/home/jenkins/workspace/Create and Upload Functional Testing/webdriver/webdriverwrapper/WebDriverWrapper.py", line 887, in execute_and_handle_webdriver_exceptions return function_to_execute() File "/home/jenkins/workspace/Create and Upload Functional Testing/webdriver/webdriverwrapper/WebDriverWrapper.py", line 212, in  return self.execute_and_handle_webdriver_exceptions(lambda: _visit(url)) File "/home/jenkins/workspace/Create and Upload Functional Testing/webdriver/webdriverwrapper/WebDriverWrapper.py", line 205, in _visit return self.driver.get(url) File "/usr/local/lib/python2.7/dist-packages/selenium/webdriver/remote/webdriver.py", line 185, in get self.execute(Command.GET, {'url': url}) File "/usr/local/lib/python2.7/dist-packages/selenium/webdriver/remote/webdriver.py", line 171, in execute response = self.command_executor.execute(driver_command, params) File "/usr/local/lib/python2.7/dist-packages/selenium/webdriver/remote/remote_connection.py", line 349, in execute return self._request(command_info[0], url, body=data) File "/usr/local/lib/python2.7/dist-packages/selenium/webdriver/remote/remote_connection.py", line 380, in _request resp = self._conn.getresponse() File "/usr/lib/python2.7/httplib.py", line 1030, in getresponse response.begin() File "/usr/lib/python2.7/httplib.py", line 407, in begin version, status, reason = self._read_status() File "/usr/lib/python2.7/httplib.py", line 371, in _read_status raise BadStatusLine(line) 

Este caso particular vino de la siguiente stack:

  • selenium == 2.44.0
  • python == 2.7.3
  • Firefox == 34.0
  • Jenkins
  • xvfb (usando el plugin jenkins para pantallas sin cabeza)

aunque hemos visto estos errores apareciendo todo el tiempo en muchas versiones diferentes de las versiones de firefox / selenium.

Ejecuté un tcpdump para capturar la solicitud real enviada justo antes de que apareciera el error BadStatusLine y obtuve lo siguiente.

  POST /hub/session/ab64574a-4a17-447a-b2e8-5b0f5ed5e923/url HTTP/1.1 Host: 127.0.0.1:41246 Accept-Encoding: identity Content-Length: 102 Connection: keep-alive Content-type: application/json;charset="UTF-8" POST: /hub/session/ab64574a-4a17-447a-b2e8-5b0f5ed5e923/url Accept: application/json User-Agent: Python http auth {"url": "http://example.com/login", "sessionId": "ab64574a-4a17-447a-b2e8-5b0f5ed5e923"} 

La respuesta vuelve con 0 bytes. Así que el BadStatusLine fue causado por una respuesta vacía, lo que tiene sentido.

La pregunta es, ¿por qué el servidor de selenium devolvería una respuesta vacía? Si el servidor muriera, ¿no obtendríamos un error de conexión o algo parecido?

Por un tiempo, no tuve ninguna reprensión ni idea de cuál era la causa. Finalmente pude repetir reproduciendo:

 import requests import json while True: requests.post('http://127.0.0.1/hub/session/', data=json.dumps({"url": "http://example.com/login", "sessionId": "ab64574a-4a17-447a-b2e8-5b0f5ed5e923"})) 

Mientras esto se estaba ejecutando, salí del navegador y obtuve un error de BadStatusLine. Cuando intenté realizar esa solicitud nuevamente, ahí fue cuando obtuve el esperado “Error de conexión” que vería desde cualquier servidor muerto.

Entonces, lo que sospecho que sucede es que cuando se envía la señal de desactivación al navegador, hay una ventana corta durante su cierre donde aún se devolverá cualquier respuesta pero con 0 bytes. Es por eso que obtiene diferentes tipos de excepciones para esencialmente el mismo problema (el navegador se muere). Resulta que teníamos un cron que estaba matando a nuestros navegadores en el fondo.